From 8dd143d5863dd3143092f18cda462f0c720b47e0 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 9 Oct 2024 16:10:46 +0300 Subject: [PATCH 01/66] initial http genertion support --- .vscode/launch.json | 21 ++ src/Kiota.Builder/GenerationLanguage.cs | 3 +- .../PathSegmenters/HttpPathSegmenter.cs | 20 ++ src/Kiota.Builder/Refiners/HttpRefiner.cs | 213 ++++++++++++++++++ .../Refiners/ILanguageRefiner.cs | 3 + .../Writers/HTTP/CodeBlockEndWriter.cs | 17 ++ .../HTTP/CodeClassDeclarationWriter.cs | 26 +++ .../Writers/HTTP/CodeEnumWriter.cs | 29 +++ .../Writers/HTTP/CodeMethodWriter.cs | 11 + .../Writers/HTTP/CodeNamespaceWriter.cs | 24 ++ .../Writers/HTTP/CodePropertyWriter.cs | 24 ++ .../CodeProprietableBlockDeclarationWriter.cs | 42 ++++ .../Writers/HTTP/HttpConventionService.cs | 126 +++++++++++ src/Kiota.Builder/Writers/HTTP/HttpWriter.cs | 18 ++ src/Kiota.Builder/Writers/LanguageWriter.cs | 2 + 15 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs create mode 100644 src/Kiota.Builder/Refiners/HttpRefiner.cs create mode 100644 src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs create mode 100644 src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs create mode 100644 src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs create mode 100644 src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs create mode 100644 src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs create mode 100644 src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs create mode 100644 src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs create mode 100644 src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs create mode 100644 src/Kiota.Builder/Writers/HTTP/HttpWriter.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index 6e77c8004f..3005619da6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,27 @@ { "version": "0.2.0", "configurations": [ + { + "name": "Launch HTTP", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll", + "args": [ + "generate", + "--openapi", + "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/Mail.yml", + "--language", + "HTTP", + "-o", + "${workspaceFolder}/samples/msgraph-mail/HTTP/src", + "-n", + "graphtypescriptv4.utilities" + ], + "cwd": "${workspaceFolder}/src/kiota", + "console": "internalConsole", + "stopAtEntry": false + }, { "name": "Launch TypeScript", "type": "coreclr", diff --git a/src/Kiota.Builder/GenerationLanguage.cs b/src/Kiota.Builder/GenerationLanguage.cs index dbd236ade7..569bbb007e 100644 --- a/src/Kiota.Builder/GenerationLanguage.cs +++ b/src/Kiota.Builder/GenerationLanguage.cs @@ -9,5 +9,6 @@ public enum GenerationLanguage Go, Swift, Ruby, - CLI + CLI, + HTTP } diff --git a/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs b/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs new file mode 100644 index 0000000000..02b7b4a6af --- /dev/null +++ b/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs @@ -0,0 +1,20 @@ +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.PathSegmenters; +public class HttpPathSegmenter(string rootPath, string clientNamespaceName) : CommonPathSegmenter(rootPath, clientNamespaceName) +{ + public override string FileSuffix => ".http"; + public override string NormalizeNamespaceSegment(string segmentName) => segmentName.ToFirstCharacterUpperCase(); + public override string NormalizeFileName(CodeElement currentElement) + { + var fileName = GetLastFileNameSegment(currentElement).ToFirstCharacterUpperCase(); + var suffix = currentElement switch + { + CodeNamespace n => n.Name.GetNamespaceImportSymbol(string.Empty), + CodeClass c => c.GetImmediateParentOfType().Name.GetNamespaceImportSymbol(string.Empty), + _ => string.Empty, + }; + return fileName + suffix; + } +} diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs new file mode 100644 index 0000000000..2c5c1ed9f7 --- /dev/null +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Configuration; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.Refiners; +public class HttpRefiner : CommonLanguageRefiner +{ + public HttpRefiner(GenerationConfiguration configuration) : base(configuration) { } + public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken cancellationToken) + { + return Task.Run(() => + { + cancellationToken.ThrowIfCancellationRequested(); + CapitalizeNamespacesFirstLetters(generatedCode); + AddRootClassForExtensions(generatedCode); + ReplaceIndexersByMethodsWithParameter( + generatedCode, + false, + static x => $"By{x.ToFirstCharacterUpperCase()}", + static x => x.ToFirstCharacterUpperCase(), + GenerationLanguage.HTTP); + cancellationToken.ThrowIfCancellationRequested(); + ReplaceReservedNames( + generatedCode, + new SwiftReservedNamesProvider(), + x => $"{x}_escaped"); + RemoveCancellationParameter(generatedCode); + ConvertUnionTypesToWrapper( + generatedCode, + _configuration.UsesBackingStore, + static s => s + ); + cancellationToken.ThrowIfCancellationRequested(); + AddPropertiesAndMethodTypesImports( + generatedCode, + true, + false, + true); + AddDefaultImports( + generatedCode, + defaultUsingEvaluators); + RemoveUntypedNodePropertyValues(generatedCode); + cancellationToken.ThrowIfCancellationRequested(); + CorrectCoreType( + generatedCode, + CorrectMethodType, + CorrectPropertyType, + CorrectImplements); + }, cancellationToken); + } + private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = { + new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter), + "MicrosoftKiotaAbstractions", "RequestAdapter"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), + "MicrosoftKiotaAbstractions", "RequestInformation", "HttpMethod", "RequestOption"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), + "MicrosoftKiotaAbstractions", "ResponseHandler"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Serializer), + "MicrosoftKiotaAbstractions", "SerializationWriter"), + new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Factory), + "MicrosoftKiotaAbstractions", "ParseNode", "Parsable"), + new (x => x is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.Model), + "MicrosoftKiotaAbstractions", "Parsable"), + new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model) && + (@class.Properties.Any(x => x.IsOfKind(CodePropertyKind.AdditionalData)) || + @class.StartBlock.Implements.Any(x => KiotaBuilder.AdditionalHolderInterface.Equals(x.Name, StringComparison.OrdinalIgnoreCase))), + "MicrosoftKiotaAbstractions", "AdditionalDataHolder"), + };//TODO add backing store types once we have them defined + private static void CorrectImplements(ProprietableBlockDeclaration block) + { + block.ReplaceImplementByName(KiotaBuilder.AdditionalHolderInterface, "AdditionalDataHolder"); + } + private static void CorrectMethodType(CodeMethod currentMethod) + { + var parentClass = currentMethod.Parent as CodeClass; + if (currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) + { + if (currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) + currentMethod.Parameters.Where(x => x.Type.Name.Equals("IResponseHandler", StringComparison.Ordinal)).ToList().ForEach(x => + { + x.Type.Name = "ResponseHandler"; + x.Type.IsNullable = false; //no pointers + }); + else if (currentMethod.IsOfKind(CodeMethodKind.RequestGenerator)) + currentMethod.ReturnType.IsNullable = true; + } + else if (currentMethod.IsOfKind(CodeMethodKind.Serializer)) + currentMethod.Parameters.Where(x => x.Type.Name.Equals("ISerializationWriter", StringComparison.Ordinal)).ToList().ForEach(x => x.Type.Name = "SerializationWriter"); + else if (currentMethod.IsOfKind(CodeMethodKind.Deserializer)) + { + currentMethod.ReturnType.Name = "[String:FieldDeserializer][String:FieldDeserializer]"; + currentMethod.Name = "getFieldDeserializers"; + } + else if (currentMethod.IsOfKind(CodeMethodKind.ClientConstructor, CodeMethodKind.Constructor, CodeMethodKind.RawUrlConstructor)) + { + var rawUrlParam = currentMethod.Parameters.OfKind(CodeParameterKind.RawUrl); + if (rawUrlParam != null) + rawUrlParam.Type.IsNullable = false; + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.RequestAdapter)) + .Where(x => x.Type.Name.StartsWith('I')) + .ToList() + .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" + } + else if (currentMethod.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility, CodeMethodKind.RequestBuilderWithParameters, CodeMethodKind.RequestBuilderBackwardCompatibility, CodeMethodKind.Factory)) + { + currentMethod.ReturnType.IsNullable = true; + if (currentMethod.Parameters.OfKind(CodeParameterKind.ParseNode) is CodeParameter parseNodeParam) + { + parseNodeParam.Type.Name = parseNodeParam.Type.Name[1..]; + parseNodeParam.Type.IsNullable = false; + } + if (currentMethod.IsOfKind(CodeMethodKind.Factory)) + currentMethod.ReturnType = new CodeType { Name = "Parsable", IsNullable = false, IsExternal = true }; + } + CorrectCoreTypes(parentClass, DateTypesReplacements, currentMethod.Parameters + .Select(x => x.Type) + .Union(new[] { currentMethod.ReturnType }) + .ToArray()); + } + private static readonly Dictionary DateTypesReplacements = new(StringComparer.OrdinalIgnoreCase) { + {"DateTimeOffset", ("Date", new CodeUsing { + Name = "Date",//TODO + Declaration = new CodeType { + Name = "Foundation", + IsExternal = true, + }, + })}, + {"TimeSpan", ("Date", new CodeUsing { + Name = "Date",//TODO + Declaration = new CodeType { + Name = "Foundation", + IsExternal = true, + }, + })}, + {"DateOnly", ("Date", new CodeUsing { + Name = "Date", + Declaration = new CodeType { + Name = "Foundation", + IsExternal = true, + }, + })}, + {"TimeOnly", ("Date", new CodeUsing { + Name = "Date",//TODO + Declaration = new CodeType { + Name = "Foundation", + IsExternal = true, + }, + })}, + }; + private static void CorrectPropertyType(CodeProperty currentProperty) + { + if (currentProperty.Type != null) + { + if (currentProperty.IsOfKind(CodePropertyKind.RequestAdapter)) + { + currentProperty.Type.IsNullable = true; + currentProperty.Type.Name = "RequestAdapter"; + } + else if (currentProperty.IsOfKind(CodePropertyKind.BackingStore)) + currentProperty.Type.Name = currentProperty.Type.Name[1..]; // removing the "I" + else if (currentProperty.IsOfKind(CodePropertyKind.AdditionalData)) + { + currentProperty.Type.IsNullable = false; + currentProperty.Type.Name = "[String:Any]"; + currentProperty.DefaultValue = $"{currentProperty.Type.Name}()"; + } + else if (currentProperty.IsOfKind(CodePropertyKind.PathParameters)) + { + currentProperty.Type.IsNullable = true; + currentProperty.Type.Name = "[String:String]"; + if (!string.IsNullOrEmpty(currentProperty.DefaultValue)) + currentProperty.DefaultValue = $"{currentProperty.Type.Name}()"; + } + else if (currentProperty.IsOfKind(CodePropertyKind.Options)) + { + currentProperty.Type.IsNullable = false; + currentProperty.Type.Name = "RequestOption"; + currentProperty.Type.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; + } + else if (currentProperty.IsOfKind(CodePropertyKind.QueryParameter) && currentProperty.Parent is CodeClass parentClass) + currentProperty.Type.Name = $"{parentClass.Name}{currentProperty.Type.Name}"; + CorrectCoreTypes(currentProperty.Parent as CodeClass, DateTypesReplacements, currentProperty.Type); + } + } + + private static void CapitalizeNamespacesFirstLetters(CodeElement current) + { + if (current is CodeNamespace currentNamespace) + currentNamespace.Name = currentNamespace.Name.Split('.').Select(static x => x.ToFirstCharacterUpperCase()).Aggregate(static (x, y) => $"{x}.{y}"); + CrawlTree(current, CapitalizeNamespacesFirstLetters); + } + private void AddRootClassForExtensions(CodeElement current) + { + if (current is CodeNamespace currentNamespace && + currentNamespace.FindNamespaceByName(_configuration.ClientNamespaceName) is CodeNamespace clientNamespace) + { + clientNamespace.AddClass(new CodeClass + { + Name = clientNamespace.Name.Split('.', StringSplitOptions.RemoveEmptyEntries).Last().ToFirstCharacterUpperCase(), + Kind = CodeClassKind.BarrelInitializer, + Documentation = new() + { + DescriptionTemplate = "Root class for extensions", + }, + }); + } + } +} diff --git a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs index 2b1b4df7d8..0ac9a5cf07 100644 --- a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs @@ -37,6 +37,9 @@ public static async Task RefineAsync(GenerationConfiguration config, CodeNamespa case GenerationLanguage.Swift: await new SwiftRefiner(config).RefineAsync(generatedCode, cancellationToken).ConfigureAwait(false); break; + case GenerationLanguage.HTTP: + await new HttpRefiner(config).RefineAsync(generatedCode, cancellationToken).ConfigureAwait(false); + break; case GenerationLanguage.Python: await new PythonRefiner(config).RefineAsync(generatedCode, cancellationToken).ConfigureAwait(false); break; diff --git a/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs new file mode 100644 index 0000000000..702f0b45fd --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs @@ -0,0 +1,17 @@ +using System; +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.http; +public class CodeBlockEndWriter : ICodeElementWriter +{ + public void WriteCodeElement(BlockEnd codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + writer.CloseBlock(); + if (codeElement?.Parent?.Parent is CodeNamespace && !(codeElement.Parent is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.BarrelInitializer))) + { + writer.CloseBlock(); + } + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs new file mode 100644 index 0000000000..3a7084161f --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.Writers.http; +public class CodeClassDeclarationWriter : CodeProprietableBlockDeclarationWriter +{ + public CodeClassDeclarationWriter(HttpConventionService conventionService) : base(conventionService) { } + protected override void WriteTypeDeclaration(ClassDeclaration codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + var derivedTypes = new List { codeElement.Inherits?.Name } + .Union(codeElement.Implements.Select(static x => x.Name)) + .Where(static x => x != null) + .ToArray(); + var derivation = derivedTypes.Length != 0 ? ": " + derivedTypes.Select(x => x.ToFirstCharacterUpperCase()).Aggregate(static (x, y) => $"{x}, {y}") + " " : string.Empty; + if (codeElement.Parent is CodeClass parentClass) + conventions.WriteShortDescription(parentClass, writer); + writer.WriteLine($"public class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); + writer.IncreaseIndent(); + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs new file mode 100644 index 0000000000..1e632b1429 --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; + +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.Writers.http; +public class CodeEnumWriter : BaseElementWriter +{ + public CodeEnumWriter(HttpConventionService conventionService) : base(conventionService) { } + public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + if (!codeElement.Options.Any()) + return; + + if (codeElement.Parent is CodeNamespace codeNamespace) + { + writer.StartBlock($"extension {codeNamespace.Name} {{"); + } + writer.StartBlock($"public enum {codeElement.Name.ToFirstCharacterUpperCase()} : String {{"); //TODO docs + writer.WriteLines(codeElement.Options + .Select(static x => x.Name.ToFirstCharacterUpperCase()) + .Select(static (x, idx) => $"case {x}")); + //TODO static parse function? + //enum and ns are closed by the code block end writer + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs new file mode 100644 index 0000000000..4b502c7a69 --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs @@ -0,0 +1,11 @@ +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.http; +public class CodeMethodWriter : BaseElementWriter +{ + public CodeMethodWriter(HttpConventionService conventionService) : base(conventionService) { } + public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) + { + // TODO (HTTP) + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs new file mode 100644 index 0000000000..254d2f2abc --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs @@ -0,0 +1,24 @@ +using System; +using System.Linq; + +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.http; +public class CodeNamespaceWriter : BaseElementWriter +{ + public CodeNamespaceWriter(HttpConventionService conventionService) : base(conventionService) { } + public override void WriteCodeElement(CodeNamespace codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + var segments = codeElement.Name.Split("."); + var lastSegment = segments.Last(); + var parentNamespaces = string.Join('.', segments[..^1]); + writer.WriteLine($"extension {parentNamespaces} {{"); + writer.IncreaseIndent(); + writer.WriteLine($"public struct {lastSegment} {{"); + writer.WriteLine("}"); + writer.DecreaseIndent(); + writer.WriteLine("}"); + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs new file mode 100644 index 0000000000..f92f62330f --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs @@ -0,0 +1,24 @@ +using System; +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.Writers.http; +public class CodePropertyWriter : BaseElementWriter +{ + public CodePropertyWriter(HttpConventionService conventionService) : base(conventionService) { } + public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + if (codeElement.Parent is not CodeElement parentElement) throw new InvalidOperationException("The parent of a property should be a class"); + var returnType = conventions.GetTypeString(codeElement.Type, parentElement); + var accessModifier = conventions.GetAccessModifier(codeElement.Access); + var defaultValue = codeElement.DefaultValue != null ? $" = {codeElement.DefaultValue}" : string.Empty; + switch (codeElement.Kind) + { + default: + writer.WriteLine($"{accessModifier} var {codeElement.Name.ToFirstCharacterLowerCase()}: {returnType}{defaultValue}"); + break; + } + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs new file mode 100644 index 0000000000..8c28859a7f --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs @@ -0,0 +1,42 @@ +using System; +using System.Linq; + +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers.http; + +public abstract class CodeProprietableBlockDeclarationWriter : BaseElementWriter + where T : ProprietableBlockDeclaration +{ + protected CodeProprietableBlockDeclarationWriter(HttpConventionService conventionService) : base(conventionService) { } + public override void WriteCodeElement(T codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + if (codeElement.Parent?.Parent is CodeNamespace) + { + var importSegments = codeElement + .Usings + .Where(static x => x.Declaration != null && x.Declaration.IsExternal) + .Select(static x => x.Declaration!.Name) + .Distinct() + .OrderBy(static x => x.Count(static y => y == '.')) + .ThenBy(x => x) + .ToList(); + if (importSegments.Count != 0) + { + importSegments.ForEach(x => writer.WriteLine($"import {x}")); + writer.WriteLine(string.Empty); + } + } + + if (codeElement.Parent?.Parent is CodeNamespace && !(codeElement.Parent is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.BarrelInitializer))) + { + writer.WriteLine($"extension {codeElement.Parent.Parent.Name} {{"); + writer.IncreaseIndent(); + } + + WriteTypeDeclaration(codeElement, writer); + } + protected abstract void WriteTypeDeclaration(T codeElement, LanguageWriter writer); +} diff --git a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs new file mode 100644 index 0000000000..8b9b048cc3 --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; + +namespace Kiota.Builder.Writers.http; +public class HttpConventionService : CommonLanguageConventionService +{ + public HttpConventionService(string clientNamespaceName) + { + ArgumentException.ThrowIfNullOrEmpty(clientNamespaceName); + this.clientNamespaceName = clientNamespaceName; + } + public override string StreamTypeName => "stream"; + public override string VoidTypeName => "void"; + public override string DocCommentPrefix => "/// "; + public static readonly char NullableMarker = '?'; + public static string NullableMarkerAsString => "?"; + public override string ParseNodeInterfaceName => "ParseNode"; + public override bool WriteShortDescription(IDocumentedElement element, LanguageWriter writer, string prefix = "", string suffix = "") + { + ArgumentNullException.ThrowIfNull(writer); + ArgumentNullException.ThrowIfNull(element); + if (!element.Documentation.DescriptionAvailable) return false; + if (element is not CodeElement codeElement) return false; + + var description = element.Documentation.GetDescription(type => GetTypeString(type, codeElement)); + writer.WriteLine($"{DocCommentPrefix}{prefix}{description}{prefix}"); + + return true; + } + public override string GetAccessModifier(AccessModifier access) + { + return access switch + { + AccessModifier.Public => "public", + AccessModifier.Protected => "internal", + _ => "private", + }; + } +#pragma warning disable CA1822 // Method should be static + internal void AddRequestBuilderBody(CodeClass parentClass, string returnType, LanguageWriter writer, string? urlTemplateVarName = default, string? prefix = default, IEnumerable? pathParameters = default) + { + if (parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProp && + parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProp) + { + var pathParametersSuffix = !(pathParameters?.Any() ?? false) ? string.Empty : $", {string.Join(", ", pathParameters.Select(static x => x.Name.ToFirstCharacterLowerCase()))}"; + var urlTplRef = urlTemplateVarName ?? pathParametersProp.Name.ToFirstCharacterUpperCase(); + writer.WriteLine($"{prefix}new {returnType}({urlTplRef}, {requestAdapterProp.Name.ToFirstCharacterUpperCase()}{pathParametersSuffix});"); + } + } + public override string TempDictionaryVarName => "urlTplParams"; +#pragma warning restore CA1822 // Method should be static + private readonly string clientNamespaceName; + private string GetNamesInUseByNamespaceSegments(CodeNamespace typeNS, CodeElement currentElement) + { + var currentNS = currentElement.GetImmediateParentOfType(); + var diffResult = currentNS.GetDifferential(typeNS, clientNamespaceName); + return diffResult.State switch + { + NamespaceDifferentialTrackerState.Same => string.Empty, + NamespaceDifferentialTrackerState.Downwards => $"{string.Join('.', diffResult.DownwardsSegments)}.", + NamespaceDifferentialTrackerState.Upwards => string.Empty, //TODO + NamespaceDifferentialTrackerState.UpwardsAndThenDownwards => $"{typeNS.Name}.", + _ => throw new NotImplementedException(), + }; + } + public override string GetTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation = true, LanguageWriter? writer = null) + { + if (code is CodeComposedTypeBase) + throw new InvalidOperationException($"Swift does not support union types, the union type {code.Name} should have been filtered out by the refiner"); + if (code is CodeType currentType) + { + var typeName = TranslateTypeAndAvoidUsingNamespaceSegmentNames(currentType, targetElement); + var nullableSuffix = code.IsNullable ? NullableMarkerAsString : string.Empty; + var collectionPrefix = currentType.IsCollection && includeCollectionInformation ? "[" : string.Empty; + var collectionSuffix = currentType.IsCollection && includeCollectionInformation ? $"]{nullableSuffix}" : string.Empty; + if (currentType.IsCollection && !string.IsNullOrEmpty(nullableSuffix)) + nullableSuffix = string.Empty; + + if (currentType.ActionOf) + return $"({collectionPrefix}{typeName}{nullableSuffix}{collectionSuffix}>) -> Void"; + return $"{collectionPrefix}{typeName}{nullableSuffix}{collectionSuffix}"; + } + + throw new InvalidOperationException($"type of type {code?.GetType()} is unknown"); + } + private string TranslateTypeAndAvoidUsingNamespaceSegmentNames(CodeType currentType, CodeElement targetElement) + { + var typeName = TranslateType(currentType); + if (currentType.TypeDefinition != null) + return $"{GetNamesInUseByNamespaceSegments(currentType.TypeDefinition.GetImmediateParentOfType(), targetElement)}{typeName}"; + return typeName; + } + public override string TranslateType(CodeType type) + { + return type?.Name switch + { + "integer" => "Int32", + "boolean" => "Bool", + "float" => "Float32", + "long" => "Int64", + "double" or "decimal" => "Float64", + "guid" => "UUID", + "void" or "uint8" or "int8" or "int32" or "int64" or "float32" or "float64" or "string" => type.Name.ToFirstCharacterUpperCase(), + "binary" or "base64" or "base64url" => "[UInt8]", + "DateTimeOffset" => "Date", // TODO, + null => "object", + _ => type.Name.ToFirstCharacterUpperCase() is string typeName && !string.IsNullOrEmpty(typeName) ? typeName : "object", + }; + } + public override string GetParameterSignature(CodeParameter parameter, CodeElement targetElement, LanguageWriter? writer = null) + { + ArgumentNullException.ThrowIfNull(parameter); + var parameterType = GetTypeString(parameter.Type, targetElement); + var defaultValue = parameter switch + { + _ when !string.IsNullOrEmpty(parameter.DefaultValue) => $" = {parameter.DefaultValue}", + _ when parameter.Optional => " = default", + _ => string.Empty, + }; + return $"{parameter.Name.ToFirstCharacterLowerCase()} : {parameterType}{defaultValue}"; + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs new file mode 100644 index 0000000000..23756c9be4 --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs @@ -0,0 +1,18 @@ +using Kiota.Builder.PathSegmenters; + +namespace Kiota.Builder.Writers.http; + +public class HttpWriter : LanguageWriter +{ + public HttpWriter(string rootPath, string clientNamespaceName) + { + PathSegmenter = new HttpPathSegmenter(rootPath, clientNamespaceName); + var conventionService = new HttpConventionService(clientNamespaceName); + AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeBlockEndWriter()); + AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeNamespaceWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); + AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); + } +} diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index 3a4a8886e0..de8b145baa 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -15,6 +15,7 @@ using Kiota.Builder.Writers.Ruby; using Kiota.Builder.Writers.Swift; using Kiota.Builder.Writers.TypeScript; +using Kiota.Builder.Writers.http; namespace Kiota.Builder.Writers; @@ -192,6 +193,7 @@ public static LanguageWriter GetLanguageWriter(GenerationLanguage language, stri GenerationLanguage.Go => new GoWriter(outputPath, clientNamespaceName, excludeBackwardCompatible), GenerationLanguage.CLI => new CliWriter(outputPath, clientNamespaceName), GenerationLanguage.Swift => new SwiftWriter(outputPath, clientNamespaceName), + GenerationLanguage.HTTP => new HttpWriter(outputPath, clientNamespaceName), _ => throw new InvalidEnumArgumentException($"{language} language currently not supported."), }; } From c7e352abee062340a1d092e7955f2bfc72e26a1c Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 22 Oct 2024 14:40:25 +0300 Subject: [PATCH 02/66] add http language generation --- .../PathSegmenters/HttpPathSegmenter.cs | 9 +-- src/Kiota.Builder/Refiners/HttpRefiner.cs | 12 +-- .../Refiners/HttpReservedNamesProvider.cs | 12 +++ .../Writers/HTTP/CodeBlockEndWriter.cs | 5 -- .../HTTP/CodeClassDeclarationWriter.cs | 78 +++++++++++++++---- .../Writers/HTTP/CodeEnumWriter.cs | 16 +--- .../Writers/HTTP/CodeMethodWriter.cs | 10 ++- .../Writers/HTTP/CodeNamespaceWriter.cs | 12 +-- .../Writers/HTTP/CodePropertyWriter.cs | 14 +--- .../CodeProprietableBlockDeclarationWriter.cs | 26 +------ .../Writers/HTTP/HttpConventionService.cs | 8 +- 11 files changed, 98 insertions(+), 104 deletions(-) create mode 100644 src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs diff --git a/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs b/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs index 02b7b4a6af..5e8db00311 100644 --- a/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs +++ b/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs @@ -8,13 +8,6 @@ public class HttpPathSegmenter(string rootPath, string clientNamespaceName) : Co public override string NormalizeNamespaceSegment(string segmentName) => segmentName.ToFirstCharacterUpperCase(); public override string NormalizeFileName(CodeElement currentElement) { - var fileName = GetLastFileNameSegment(currentElement).ToFirstCharacterUpperCase(); - var suffix = currentElement switch - { - CodeNamespace n => n.Name.GetNamespaceImportSymbol(string.Empty), - CodeClass c => c.GetImmediateParentOfType().Name.GetNamespaceImportSymbol(string.Empty), - _ => string.Empty, - }; - return fileName + suffix; + return GetLastFileNameSegment(currentElement).ToFirstCharacterUpperCase(); } } diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 2c5c1ed9f7..19d248c297 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -27,7 +27,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken cancellationToken.ThrowIfCancellationRequested(); ReplaceReservedNames( generatedCode, - new SwiftReservedNamesProvider(), + new HttpReservedNamesProvider(), x => $"{x}_escaped"); RemoveCancellationParameter(generatedCode); ConvertUnionTypesToWrapper( @@ -70,7 +70,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken (@class.Properties.Any(x => x.IsOfKind(CodePropertyKind.AdditionalData)) || @class.StartBlock.Implements.Any(x => KiotaBuilder.AdditionalHolderInterface.Equals(x.Name, StringComparison.OrdinalIgnoreCase))), "MicrosoftKiotaAbstractions", "AdditionalDataHolder"), - };//TODO add backing store types once we have them defined + }; private static void CorrectImplements(ProprietableBlockDeclaration block) { block.ReplaceImplementByName(KiotaBuilder.AdditionalHolderInterface, "AdditionalDataHolder"); @@ -124,14 +124,14 @@ private static void CorrectMethodType(CodeMethod currentMethod) } private static readonly Dictionary DateTypesReplacements = new(StringComparer.OrdinalIgnoreCase) { {"DateTimeOffset", ("Date", new CodeUsing { - Name = "Date",//TODO + Name = "Date", Declaration = new CodeType { Name = "Foundation", IsExternal = true, }, })}, {"TimeSpan", ("Date", new CodeUsing { - Name = "Date",//TODO + Name = "Date", Declaration = new CodeType { Name = "Foundation", IsExternal = true, @@ -145,7 +145,7 @@ private static void CorrectMethodType(CodeMethod currentMethod) }, })}, {"TimeOnly", ("Date", new CodeUsing { - Name = "Date",//TODO + Name = "Date", Declaration = new CodeType { Name = "Foundation", IsExternal = true, diff --git a/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs b/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs new file mode 100644 index 0000000000..4ce7599837 --- /dev/null +++ b/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; + +namespace Kiota.Builder.Refiners; +public class HttpReservedNamesProvider : IReservedNamesProvider +{ + private readonly Lazy> _reservedNames = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { + "any" + // TODO (HTTP) add full list + }); + public HashSet ReservedNames => _reservedNames.Value; +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs index 702f0b45fd..c7e384da90 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs @@ -8,10 +8,5 @@ public void WriteCodeElement(BlockEnd codeElement, LanguageWriter writer) { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - writer.CloseBlock(); - if (codeElement?.Parent?.Parent is CodeNamespace && !(codeElement.Parent is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.BarrelInitializer))) - { - writer.CloseBlock(); - } } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 3a7084161f..61d927e547 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -1,26 +1,78 @@ using System; -using System.Collections.Generic; using System.Linq; using Kiota.Builder.CodeDOM; -using Kiota.Builder.Extensions; namespace Kiota.Builder.Writers.http; -public class CodeClassDeclarationWriter : CodeProprietableBlockDeclarationWriter +public class CodeClassDeclarationWriter(HttpConventionService conventionService) : CodeProprietableBlockDeclarationWriter(conventionService) { - public CodeClassDeclarationWriter(HttpConventionService conventionService) : base(conventionService) { } protected override void WriteTypeDeclaration(ClassDeclaration codeElement, LanguageWriter writer) { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - var derivedTypes = new List { codeElement.Inherits?.Name } - .Union(codeElement.Implements.Select(static x => x.Name)) - .Where(static x => x != null) - .ToArray(); - var derivation = derivedTypes.Length != 0 ? ": " + derivedTypes.Select(x => x.ToFirstCharacterUpperCase()).Aggregate(static (x, y) => $"{x}, {y}") + " " : string.Empty; - if (codeElement.Parent is CodeClass parentClass) - conventions.WriteShortDescription(parentClass, writer); - writer.WriteLine($"public class {codeElement.Name.ToFirstCharacterUpperCase()} {derivation}{{"); - writer.IncreaseIndent(); + writer.WriteLine(); + + if (codeElement.Parent is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.RequestBuilder)) + { + conventions.WriteShortDescription(codeClass, writer); + // its a request builder class + + // TODO: write the baseUrl variable e.g @baseUrl = http://loccalhost:3000 + + // extract the URL Template + var urlTemplateProperty = codeElement.Parent + .GetChildElements(true) + .OfType() + .FirstOrDefault(property => property.IsOfKind(CodePropertyKind.UrlTemplate)); + + var urlTemplate = urlTemplateProperty?.DefaultValue; + // Write the URL template comment + writer.WriteLine($"# {urlTemplateProperty?.Documentation?.DescriptionTemplate}"); + writer.WriteLine($"# {urlTemplate}"); + writer.WriteLine(); + + // TODO: write path variables e.g @post-id = + + // Write all the query parameter variables + var queryParameterClasses = codeElement.Parent? + .GetChildElements(true) + .OfType() + .Where(element => element.IsOfKind(CodeClassKind.QueryParameters)) + .ToList(); + queryParameterClasses?.ForEach(paramCodeClass => + { + // Write all query parameters + // Select all properties of type query parameters + var queryParams = paramCodeClass + .Properties + .Where(property => property.IsOfKind(CodePropertyKind.QueryParameter)) + .ToList(); + + queryParams.ForEach(prop => { + // Write the documentation + var documentation = prop.Documentation.DescriptionTemplate; + writer.WriteLine($"# {documentation}"); + writer.WriteLine($"@{prop.Name} = "); + writer.WriteLine(); + }); + }); + + // Write all http methods + var httpMethods = codeElement.Parent? + .GetChildElements(true) + .OfType() + .Where(element => element.IsOfKind(CodeMethodKind.RequestExecutor)) + .ToList(); + httpMethods?.ForEach(method => + { + // Write http operations e.g GET, POST, DELETE e.t.c + // Get the documentation + var documentation = method.Documentation.DescriptionTemplate; + writer.WriteLine($"# {documentation}"); + writer.WriteLine($"{method.Name.ToUpperInvariant()} {urlTemplate}"); + writer.WriteLine("###"); + writer.WriteLine(""); + }); + } } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs index 1e632b1429..433643449f 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs @@ -5,25 +5,11 @@ using Kiota.Builder.Extensions; namespace Kiota.Builder.Writers.http; -public class CodeEnumWriter : BaseElementWriter +public class CodeEnumWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) { - public CodeEnumWriter(HttpConventionService conventionService) : base(conventionService) { } public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter writer) { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - if (!codeElement.Options.Any()) - return; - - if (codeElement.Parent is CodeNamespace codeNamespace) - { - writer.StartBlock($"extension {codeNamespace.Name} {{"); - } - writer.StartBlock($"public enum {codeElement.Name.ToFirstCharacterUpperCase()} : String {{"); //TODO docs - writer.WriteLines(codeElement.Options - .Select(static x => x.Name.ToFirstCharacterUpperCase()) - .Select(static (x, idx) => $"case {x}")); - //TODO static parse function? - //enum and ns are closed by the code block end writer } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs index 4b502c7a69..b894ef9bbd 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs @@ -1,11 +1,13 @@ -using Kiota.Builder.CodeDOM; +using System; +using System.Linq; +using Kiota.Builder.CodeDOM; namespace Kiota.Builder.Writers.http; -public class CodeMethodWriter : BaseElementWriter +public class CodeMethodWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) { - public CodeMethodWriter(HttpConventionService conventionService) : base(conventionService) { } public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) { - // TODO (HTTP) + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs index 254d2f2abc..205998912e 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs @@ -4,21 +4,11 @@ using Kiota.Builder.CodeDOM; namespace Kiota.Builder.Writers.http; -public class CodeNamespaceWriter : BaseElementWriter +public class CodeNamespaceWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) { - public CodeNamespaceWriter(HttpConventionService conventionService) : base(conventionService) { } public override void WriteCodeElement(CodeNamespace codeElement, LanguageWriter writer) { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - var segments = codeElement.Name.Split("."); - var lastSegment = segments.Last(); - var parentNamespaces = string.Join('.', segments[..^1]); - writer.WriteLine($"extension {parentNamespaces} {{"); - writer.IncreaseIndent(); - writer.WriteLine($"public struct {lastSegment} {{"); - writer.WriteLine("}"); - writer.DecreaseIndent(); - writer.WriteLine("}"); } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs index f92f62330f..92a473204c 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs @@ -3,22 +3,12 @@ using Kiota.Builder.Extensions; namespace Kiota.Builder.Writers.http; -public class CodePropertyWriter : BaseElementWriter +public class CodePropertyWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) { - public CodePropertyWriter(HttpConventionService conventionService) : base(conventionService) { } public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter writer) { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - if (codeElement.Parent is not CodeElement parentElement) throw new InvalidOperationException("The parent of a property should be a class"); - var returnType = conventions.GetTypeString(codeElement.Type, parentElement); - var accessModifier = conventions.GetAccessModifier(codeElement.Access); - var defaultValue = codeElement.DefaultValue != null ? $" = {codeElement.DefaultValue}" : string.Empty; - switch (codeElement.Kind) - { - default: - writer.WriteLine($"{accessModifier} var {codeElement.Name.ToFirstCharacterLowerCase()}: {returnType}{defaultValue}"); - break; - } + if (codeElement.Parent is null) throw new InvalidOperationException("The parent of a property should be a class"); } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs index 8c28859a7f..bd752c127c 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs @@ -5,37 +5,13 @@ namespace Kiota.Builder.Writers.http; -public abstract class CodeProprietableBlockDeclarationWriter : BaseElementWriter +public abstract class CodeProprietableBlockDeclarationWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) where T : ProprietableBlockDeclaration { - protected CodeProprietableBlockDeclarationWriter(HttpConventionService conventionService) : base(conventionService) { } public override void WriteCodeElement(T codeElement, LanguageWriter writer) { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - if (codeElement.Parent?.Parent is CodeNamespace) - { - var importSegments = codeElement - .Usings - .Where(static x => x.Declaration != null && x.Declaration.IsExternal) - .Select(static x => x.Declaration!.Name) - .Distinct() - .OrderBy(static x => x.Count(static y => y == '.')) - .ThenBy(x => x) - .ToList(); - if (importSegments.Count != 0) - { - importSegments.ForEach(x => writer.WriteLine($"import {x}")); - writer.WriteLine(string.Empty); - } - } - - if (codeElement.Parent?.Parent is CodeNamespace && !(codeElement.Parent is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.BarrelInitializer))) - { - writer.WriteLine($"extension {codeElement.Parent.Parent.Name} {{"); - writer.IncreaseIndent(); - } - WriteTypeDeclaration(codeElement, writer); } protected abstract void WriteTypeDeclaration(T codeElement, LanguageWriter writer); diff --git a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs index 8b9b048cc3..e4c14b91a6 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs @@ -15,11 +15,11 @@ public HttpConventionService(string clientNamespaceName) } public override string StreamTypeName => "stream"; public override string VoidTypeName => "void"; - public override string DocCommentPrefix => "/// "; + public override string DocCommentPrefix => "###"; public static readonly char NullableMarker = '?'; public static string NullableMarkerAsString => "?"; public override string ParseNodeInterfaceName => "ParseNode"; - public override bool WriteShortDescription(IDocumentedElement element, LanguageWriter writer, string prefix = "", string suffix = "") + public override bool WriteShortDescription(IDocumentedElement element, LanguageWriter writer, string prefix = "", string suffix = "") { ArgumentNullException.ThrowIfNull(writer); ArgumentNullException.ThrowIfNull(element); @@ -69,8 +69,6 @@ private string GetNamesInUseByNamespaceSegments(CodeNamespace typeNS, CodeElemen } public override string GetTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation = true, LanguageWriter? writer = null) { - if (code is CodeComposedTypeBase) - throw new InvalidOperationException($"Swift does not support union types, the union type {code.Name} should have been filtered out by the refiner"); if (code is CodeType currentType) { var typeName = TranslateTypeAndAvoidUsingNamespaceSegmentNames(currentType, targetElement); @@ -106,7 +104,7 @@ public override string TranslateType(CodeType type) "guid" => "UUID", "void" or "uint8" or "int8" or "int32" or "int64" or "float32" or "float64" or "string" => type.Name.ToFirstCharacterUpperCase(), "binary" or "base64" or "base64url" => "[UInt8]", - "DateTimeOffset" => "Date", // TODO, + "DateTimeOffset" => "Date", null => "object", _ => type.Name.ToFirstCharacterUpperCase() is string typeName && !string.IsNullOrEmpty(typeName) ? typeName : "object", }; From c2c7443645867de1475b0caa3887ae77a9ebefcf Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 13 Nov 2024 18:20:10 +0300 Subject: [PATCH 03/66] remove unused code elements from the DOM --- .../PathSegmenters/HttpPathSegmenter.cs | 2 +- src/Kiota.Builder/Refiners/HttpRefiner.cs | 54 ++++++ .../HTTP/CodeClassDeclarationWriter.cs | 169 +++++++++++++----- src/Kiota.Builder/Writers/LanguageWriter.cs | 3 +- 4 files changed, 176 insertions(+), 52 deletions(-) diff --git a/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs b/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs index 5e8db00311..052da6654f 100644 --- a/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs +++ b/src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs @@ -1,4 +1,4 @@ -using Kiota.Builder.CodeDOM; +using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; namespace Kiota.Builder.PathSegmenters; diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 19d248c297..f9b73166c9 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -46,6 +46,9 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken defaultUsingEvaluators); RemoveUntypedNodePropertyValues(generatedCode); cancellationToken.ThrowIfCancellationRequested(); + SetBaseUrlForRequestBuilderMethods(generatedCode, GetBaseUrl(generatedCode)); + // Remove unused code from the DOM e.g Models + RemoveUnusedCodeElements(generatedCode); CorrectCoreType( generatedCode, CorrectMethodType, @@ -75,6 +78,18 @@ private static void CorrectImplements(ProprietableBlockDeclaration block) { block.ReplaceImplementByName(KiotaBuilder.AdditionalHolderInterface, "AdditionalDataHolder"); } + + private string? GetBaseUrl(CodeElement element) + { + return element.GetImmediateParentOfType() + .GetRootNamespace()? + .FindChildrenByName(_configuration.ClientClassName)? + .FirstOrDefault()? + .Methods? + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.ClientConstructor))? + .BaseUrl; + } + private static void CorrectMethodType(CodeMethod currentMethod) { var parentClass = currentMethod.Parent as CodeClass; @@ -194,6 +209,25 @@ private static void CapitalizeNamespacesFirstLetters(CodeElement current) currentNamespace.Name = currentNamespace.Name.Split('.').Select(static x => x.ToFirstCharacterUpperCase()).Aggregate(static (x, y) => $"{x}.{y}"); CrawlTree(current, CapitalizeNamespacesFirstLetters); } + + private static void SetBaseUrlForRequestBuilderMethods(CodeElement current, string? baseUrl) + { + if (baseUrl is not null && current is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.RequestBuilder)) + { + // Add a new property named BaseUrl and set its value to the baseUrl string + var baseUrlProperty = new CodeProperty + { + Name = "BaseUrl", + Kind = CodePropertyKind.Custom, + Access = AccessModifier.Private, + DefaultValue = baseUrl, + Type = new CodeType { Name = "string", IsExternal = true } + }; + codeClass.AddProperty(baseUrlProperty); + } + CrawlTree(current, (element) => SetBaseUrlForRequestBuilderMethods(element, baseUrl)); + } + private void AddRootClassForExtensions(CodeElement current) { if (current is CodeNamespace currentNamespace && @@ -210,4 +244,24 @@ private void AddRootClassForExtensions(CodeElement current) }); } } + + private static void RemoveUnusedCodeElements(CodeElement currentElement) + { + if (currentElement is CodeClass currentClass) + { + if (currentClass.IsOfKind(CodeClassKind.Model) || currentClass.IsOfKind(CodeClassKind.BarrelInitializer) || IsBaseRequestBuilder(currentClass)) + { + var parentNameSpace = currentElement.GetImmediateParentOfType(); + parentNameSpace?.RemoveChildElement(currentElement); + } + } + + CrawlTree(currentElement, RemoveUnusedCodeElements); + } + + private static bool IsBaseRequestBuilder(CodeClass codeClass) + { + return codeClass.IsOfKind(CodeClassKind.RequestBuilder) && + codeClass.Properties.Any(property => property.IsOfKind(CodePropertyKind.UrlTemplate) && string.Equals(property.DefaultValue, "\"{+baseurl}\"", StringComparison.Ordinal)); + } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 61d927e547..b18a052af2 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -10,69 +10,140 @@ protected override void WriteTypeDeclaration(ClassDeclaration codeElement, Langu { ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - writer.WriteLine(); if (codeElement.Parent is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.RequestBuilder)) { + // Write short description conventions.WriteShortDescription(codeClass, writer); - // its a request builder class + writer.WriteLine(); - // TODO: write the baseUrl variable e.g @baseUrl = http://loccalhost:3000 + // Write the baseUrl variable + WriteBaseUrl(codeClass, writer); - // extract the URL Template - var urlTemplateProperty = codeElement.Parent - .GetChildElements(true) - .OfType() - .FirstOrDefault(property => property.IsOfKind(CodePropertyKind.UrlTemplate)); + // Extract and write the URL template + WriteUrlTemplate(codeElement, writer); - var urlTemplate = urlTemplateProperty?.DefaultValue; - // Write the URL template comment - writer.WriteLine($"# {urlTemplateProperty?.Documentation?.DescriptionTemplate}"); - writer.WriteLine($"# {urlTemplate}"); - writer.WriteLine(); + // Write all query parameter variables + WriteQueryParameters(codeElement, writer); - // TODO: write path variables e.g @post-id = + // Write all HTTP methods GET, POST, PUT, DELETE e.t.c + WriteHttpMethods(codeElement, writer); + } + } - // Write all the query parameter variables - var queryParameterClasses = codeElement.Parent? - .GetChildElements(true) - .OfType() - .Where(element => element.IsOfKind(CodeClassKind.QueryParameters)) - .ToList(); - queryParameterClasses?.ForEach(paramCodeClass => - { - // Write all query parameters - // Select all properties of type query parameters - var queryParams = paramCodeClass - .Properties - .Where(property => property.IsOfKind(CodePropertyKind.QueryParameter)) - .ToList(); - - queryParams.ForEach(prop => { - // Write the documentation - var documentation = prop.Documentation.DescriptionTemplate; - writer.WriteLine($"# {documentation}"); - writer.WriteLine($"@{prop.Name} = "); - writer.WriteLine(); - }); - }); + private static void WriteBaseUrl(CodeClass codeClass, LanguageWriter writer) + { + var baseUrl = codeClass.Properties.FirstOrDefault(property => property.Name.Equals("BaseUrl", StringComparison.OrdinalIgnoreCase))?.DefaultValue; + writer.WriteLine($"# baseUrl"); + writer.WriteLine($"@baseUrl = {baseUrl}"); + writer.WriteLine(); + } - // Write all http methods - var httpMethods = codeElement.Parent? - .GetChildElements(true) - .OfType() - .Where(element => element.IsOfKind(CodeMethodKind.RequestExecutor)) + private static void WriteUrlTemplate(CodeElement codeElement, LanguageWriter writer) + { + var urlTemplateProperty = codeElement.Parent? + .GetChildElements(true) + .OfType() + .FirstOrDefault(property => property.IsOfKind(CodePropertyKind.UrlTemplate)); + + var urlTemplate = urlTemplateProperty?.DefaultValue; + writer.WriteLine($"# {urlTemplateProperty?.Documentation?.DescriptionTemplate}"); + writer.WriteLine($"# {urlTemplate}"); + writer.WriteLine(); + } + + private static void WriteQueryParameters(CodeElement codeElement, LanguageWriter writer) + { + var queryParameterClasses = codeElement.Parent? + .GetChildElements(true) + .OfType() + .Where(element => element.IsOfKind(CodeClassKind.QueryParameters)) + .ToList(); + + queryParameterClasses?.ForEach(paramCodeClass => + { + var queryParams = paramCodeClass + .Properties + .Where(property => property.IsOfKind(CodePropertyKind.QueryParameter)) .ToList(); - httpMethods?.ForEach(method => + + queryParams.ForEach(prop => { - // Write http operations e.g GET, POST, DELETE e.t.c - // Get the documentation - var documentation = method.Documentation.DescriptionTemplate; + var documentation = prop.Documentation.DescriptionTemplate; writer.WriteLine($"# {documentation}"); - writer.WriteLine($"{method.Name.ToUpperInvariant()} {urlTemplate}"); - writer.WriteLine("###"); - writer.WriteLine(""); + writer.WriteLine($"@{prop.Name} = "); + writer.WriteLine(); }); + }); + } + + private static void WriteHttpMethods(CodeElement codeElement, LanguageWriter writer) + { + var httpMethods = codeElement.Parent? + .GetChildElements(true) + .OfType() + .Where(element => element.IsOfKind(CodeMethodKind.RequestExecutor)) + .ToList(); + + httpMethods?.ForEach(method => + { + var documentation = method.Documentation.DescriptionTemplate; + writer.WriteLine($"# {documentation}"); + writer.WriteLine($"{method.Name.ToUpperInvariant()} {GetUrlTemplate(codeElement)}"); + + WriteRequestBody(method, writer); + + writer.WriteLine(); + writer.WriteLine("###"); + writer.WriteLine(); + }); + } + + private static string GetUrlTemplate(CodeElement codeElement) + { + var urlTemplateProperty = codeElement.Parent? + .GetChildElements(true) + .OfType() + .FirstOrDefault(property => property.IsOfKind(CodePropertyKind.UrlTemplate)); + + return urlTemplateProperty?.DefaultValue ?? string.Empty; + } + + private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) + { + // If there is a request body, write it + var requestBody = method.Parameters.FirstOrDefault(param => param.IsOfKind(CodeParameterKind.RequestBody)); + if (requestBody is null) return; + + var contentType = method.RequestBodyContentType; + // Empty line before content type + writer.WriteLine(); + writer.WriteLine($"Content-Type: {contentType}"); + + // loop through the properties of the request body and write a JSON object + if (requestBody.Type is CodeType ct && ct.TypeDefinition is CodeClass requestBodyClass) + { + writer.WriteLine("{"); + writer.IncreaseIndent(); + foreach (var prop in requestBodyClass.Properties.Where(prop => prop.IsOfKind(CodePropertyKind.Custom))) + { + writer.WriteLine($"{prop.Name}: {GetDefaultValueForProperty(prop)}"); + } + writer.DecreaseIndent(); + writer.WriteLine("}"); } } + + private static string GetDefaultValueForProperty(CodeProperty prop) + { + return prop.Type.Name switch + { + "int" or "integer" => "0", + "string" => "\"string\"", + "bool" or "boolean" => "false", + _ when prop.Type is CodeType enumType && enumType.TypeDefinition is CodeEnum enumDefinition => + enumDefinition.Options.FirstOrDefault()?.Name ?? "null", + _ => "null" + }; + } } diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index de8b145baa..de88cf46b2 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -3,19 +3,18 @@ using System.ComponentModel; using System.IO; using System.Linq; - using Kiota.Builder.CodeDOM; using Kiota.Builder.PathSegmenters; using Kiota.Builder.Writers.Cli; using Kiota.Builder.Writers.CSharp; using Kiota.Builder.Writers.Go; +using Kiota.Builder.Writers.http; using Kiota.Builder.Writers.Java; using Kiota.Builder.Writers.Php; using Kiota.Builder.Writers.Python; using Kiota.Builder.Writers.Ruby; using Kiota.Builder.Writers.Swift; using Kiota.Builder.Writers.TypeScript; -using Kiota.Builder.Writers.http; namespace Kiota.Builder.Writers; From 3a25dab3345aafa230ca5800e3f41b06696c864d Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 14 Nov 2024 11:42:31 +0300 Subject: [PATCH 04/66] merge if statements --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index f9b73166c9..8db9b0686c 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -245,18 +245,15 @@ private void AddRootClassForExtensions(CodeElement current) } } - private static void RemoveUnusedCodeElements(CodeElement currentElement) + private static void RemoveUnusedCodeElements(CodeElement element) { - if (currentElement is CodeClass currentClass) + if (element is CodeClass code && (code.IsOfKind(CodeClassKind.Model) || code.IsOfKind(CodeClassKind.BarrelInitializer) || IsBaseRequestBuilder(code))) { - if (currentClass.IsOfKind(CodeClassKind.Model) || currentClass.IsOfKind(CodeClassKind.BarrelInitializer) || IsBaseRequestBuilder(currentClass)) - { - var parentNameSpace = currentElement.GetImmediateParentOfType(); - parentNameSpace?.RemoveChildElement(currentElement); - } + var parentNameSpace = element.GetImmediateParentOfType(); + parentNameSpace?.RemoveChildElement(element); } - CrawlTree(currentElement, RemoveUnusedCodeElements); + CrawlTree(element, RemoveUnusedCodeElements); } private static bool IsBaseRequestBuilder(CodeClass codeClass) From 5154280ff85d6b917bbdd2e5f0f8692c41ae1b1c Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 14 Nov 2024 12:35:13 +0300 Subject: [PATCH 05/66] remove unused code in the refiner --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 8db9b0686c..165c4cb354 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -17,7 +17,6 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken { cancellationToken.ThrowIfCancellationRequested(); CapitalizeNamespacesFirstLetters(generatedCode); - AddRootClassForExtensions(generatedCode); ReplaceIndexersByMethodsWithParameter( generatedCode, false, @@ -44,10 +43,9 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken AddDefaultImports( generatedCode, defaultUsingEvaluators); - RemoveUntypedNodePropertyValues(generatedCode); cancellationToken.ThrowIfCancellationRequested(); SetBaseUrlForRequestBuilderMethods(generatedCode, GetBaseUrl(generatedCode)); - // Remove unused code from the DOM e.g Models + // Remove unused code from the DOM e.g Models, BarrelInitializers, e.t.c RemoveUnusedCodeElements(generatedCode); CorrectCoreType( generatedCode, @@ -228,23 +226,6 @@ private static void SetBaseUrlForRequestBuilderMethods(CodeElement current, stri CrawlTree(current, (element) => SetBaseUrlForRequestBuilderMethods(element, baseUrl)); } - private void AddRootClassForExtensions(CodeElement current) - { - if (current is CodeNamespace currentNamespace && - currentNamespace.FindNamespaceByName(_configuration.ClientNamespaceName) is CodeNamespace clientNamespace) - { - clientNamespace.AddClass(new CodeClass - { - Name = clientNamespace.Name.Split('.', StringSplitOptions.RemoveEmptyEntries).Last().ToFirstCharacterUpperCase(), - Kind = CodeClassKind.BarrelInitializer, - Documentation = new() - { - DescriptionTemplate = "Root class for extensions", - }, - }); - } - } - private static void RemoveUnusedCodeElements(CodeElement element) { if (element is CodeClass code && (code.IsOfKind(CodeClassKind.Model) || code.IsOfKind(CodeClassKind.BarrelInitializer) || IsBaseRequestBuilder(code))) From bac834f095acbf36bac9e5413c84b9ea8fac1cb0 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 14 Nov 2024 14:15:39 +0300 Subject: [PATCH 06/66] write all properties including ones in the base class --- .../HTTP/CodeClassDeclarationWriter.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index b18a052af2..1aee645f9f 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -125,15 +125,27 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) { writer.WriteLine("{"); writer.IncreaseIndent(); - foreach (var prop in requestBodyClass.Properties.Where(prop => prop.IsOfKind(CodePropertyKind.Custom))) - { - writer.WriteLine($"{prop.Name}: {GetDefaultValueForProperty(prop)}"); - } + WriteProperties(requestBodyClass, writer); writer.DecreaseIndent(); writer.WriteLine("}"); } } + private static void WriteProperties(CodeClass codeClass, LanguageWriter writer) + { + // Write properties of the current class + foreach (var prop in codeClass.Properties.Where(prop => prop.IsOfKind(CodePropertyKind.Custom))) + { + writer.WriteLine($"{prop.Name}: {GetDefaultValueForProperty(prop)}"); + } + + // If the class extends another class, write properties of the base class + if (codeClass.StartBlock.Inherits?.TypeDefinition is CodeClass baseClass) + { + WriteProperties(baseClass, writer); + } + } + private static string GetDefaultValueForProperty(CodeProperty prop) { return prop.Type.Name switch From 31c3c532ff3a63f27a642020b5c3f8a93728d29c Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 15 Nov 2024 17:11:32 +0300 Subject: [PATCH 07/66] Remove unused code and enhance JSON request body output --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 45 ++++++------------- .../HTTP/CodeClassDeclarationWriter.cs | 44 +++++++++++++----- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 165c4cb354..9f2e621337 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -35,15 +35,6 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken static s => s ); cancellationToken.ThrowIfCancellationRequested(); - AddPropertiesAndMethodTypesImports( - generatedCode, - true, - false, - true); - AddDefaultImports( - generatedCode, - defaultUsingEvaluators); - cancellationToken.ThrowIfCancellationRequested(); SetBaseUrlForRequestBuilderMethods(generatedCode, GetBaseUrl(generatedCode)); // Remove unused code from the DOM e.g Models, BarrelInitializers, e.t.c RemoveUnusedCodeElements(generatedCode); @@ -54,24 +45,6 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken CorrectImplements); }, cancellationToken); } - private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = { - new (x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter), - "MicrosoftKiotaAbstractions", "RequestAdapter"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator), - "MicrosoftKiotaAbstractions", "RequestInformation", "HttpMethod", "RequestOption"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor), - "MicrosoftKiotaAbstractions", "ResponseHandler"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Serializer), - "MicrosoftKiotaAbstractions", "SerializationWriter"), - new (x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer, CodeMethodKind.Factory), - "MicrosoftKiotaAbstractions", "ParseNode", "Parsable"), - new (x => x is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.Model), - "MicrosoftKiotaAbstractions", "Parsable"), - new (x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model) && - (@class.Properties.Any(x => x.IsOfKind(CodePropertyKind.AdditionalData)) || - @class.StartBlock.Implements.Any(x => KiotaBuilder.AdditionalHolderInterface.Equals(x.Name, StringComparison.OrdinalIgnoreCase))), - "MicrosoftKiotaAbstractions", "AdditionalDataHolder"), - }; private static void CorrectImplements(ProprietableBlockDeclaration block) { block.ReplaceImplementByName(KiotaBuilder.AdditionalHolderInterface, "AdditionalDataHolder"); @@ -228,18 +201,28 @@ private static void SetBaseUrlForRequestBuilderMethods(CodeElement current, stri private static void RemoveUnusedCodeElements(CodeElement element) { - if (element is CodeClass code && (code.IsOfKind(CodeClassKind.Model) || code.IsOfKind(CodeClassKind.BarrelInitializer) || IsBaseRequestBuilder(code))) + if (!IsRequestBuilderClass(element) || IsBaseRequestBuilder(element) || IsRequestBuilderClassWithoutAnyHttpOperations(element)) { var parentNameSpace = element.GetImmediateParentOfType(); parentNameSpace?.RemoveChildElement(element); } - CrawlTree(element, RemoveUnusedCodeElements); } - private static bool IsBaseRequestBuilder(CodeClass codeClass) + private static bool IsRequestBuilderClass(CodeElement element) { - return codeClass.IsOfKind(CodeClassKind.RequestBuilder) && + return element is CodeClass code && code.IsOfKind(CodeClassKind.RequestBuilder); + } + + private static bool IsBaseRequestBuilder(CodeElement element) + { + return element is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.RequestBuilder) && codeClass.Properties.Any(property => property.IsOfKind(CodePropertyKind.UrlTemplate) && string.Equals(property.DefaultValue, "\"{+baseurl}\"", StringComparison.Ordinal)); } + + private static bool IsRequestBuilderClassWithoutAnyHttpOperations(CodeElement element) + { + return element is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.RequestBuilder) && + !codeClass.Methods.Any(method => method.IsOfKind(CodeMethodKind.RequestExecutor)); + } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 1aee645f9f..1579ee840b 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -69,8 +69,7 @@ private static void WriteQueryParameters(CodeElement codeElement, LanguageWriter queryParams.ForEach(prop => { - var documentation = prop.Documentation.DescriptionTemplate; - writer.WriteLine($"# {documentation}"); + writer.WriteLine($"# {prop.Documentation.DescriptionTemplate}"); writer.WriteLine($"@{prop.Name} = "); writer.WriteLine(); }); @@ -87,8 +86,7 @@ private static void WriteHttpMethods(CodeElement codeElement, LanguageWriter wri httpMethods?.ForEach(method => { - var documentation = method.Documentation.DescriptionTemplate; - writer.WriteLine($"# {documentation}"); + writer.WriteLine($"# {method.Documentation.DescriptionTemplate}"); writer.WriteLine($"{method.Name.ToUpperInvariant()} {GetUrlTemplate(codeElement)}"); WriteRequestBody(method, writer); @@ -115,10 +113,9 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) var requestBody = method.Parameters.FirstOrDefault(param => param.IsOfKind(CodeParameterKind.RequestBody)); if (requestBody is null) return; - var contentType = method.RequestBodyContentType; // Empty line before content type writer.WriteLine(); - writer.WriteLine($"Content-Type: {contentType}"); + writer.WriteLine($"Content-Type: {method.RequestBodyContentType}"); // loop through the properties of the request body and write a JSON object if (requestBody.Type is CodeType ct && ct.TypeDefinition is CodeClass requestBodyClass) @@ -130,13 +127,37 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) writer.WriteLine("}"); } } - private static void WriteProperties(CodeClass codeClass, LanguageWriter writer) { - // Write properties of the current class - foreach (var prop in codeClass.Properties.Where(prop => prop.IsOfKind(CodePropertyKind.Custom))) + var properties = codeClass.Properties.Where(prop => prop.IsOfKind(CodePropertyKind.Custom)).ToList(); + for (int i = 0; i < properties.Count; i++) { - writer.WriteLine($"{prop.Name}: {GetDefaultValueForProperty(prop)}"); + var prop = properties[i]; + var propName = $"\"{prop.Name}\""; + writer.Write($"{propName}: "); + if (prop.Type is CodeType propType && propType.TypeDefinition is CodeClass propClass) + { + // If the property is an object, write a JSON representation recursively + writer.WriteLine("{", includeIndent: false); + writer.IncreaseIndent(); + WriteProperties(propClass, writer); + writer.DecreaseIndent(); + writer.Write("}"); + } + else + { + writer.Write(GetDefaultValueForProperty(prop), includeIndent: false); + } + + // Add a trailing comma if there are more properties to be written + if (i < properties.Count - 1) + { + writer.WriteLine(",", includeIndent: false); + } + else + { + writer.WriteLine(); + } } // If the class extends another class, write properties of the base class @@ -154,8 +175,9 @@ private static string GetDefaultValueForProperty(CodeProperty prop) "string" => "\"string\"", "bool" or "boolean" => "false", _ when prop.Type is CodeType enumType && enumType.TypeDefinition is CodeEnum enumDefinition => - enumDefinition.Options.FirstOrDefault()?.Name ?? "null", + enumDefinition.Options.FirstOrDefault()?.Name is string enumName ? $"\"{enumName}\"" : "null", _ => "null" }; } + } From 0ec58159d47867a0e910ef92cdd30693720102f9 Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 18 Nov 2024 14:53:10 +0300 Subject: [PATCH 08/66] decode the parameter names before writing to http snippet files --- .../Writers/HTTP/CodeClassDeclarationWriter.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 1579ee840b..65038dcab4 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -1,6 +1,6 @@ using System; using System.Linq; - +using System.Web; using Kiota.Builder.CodeDOM; namespace Kiota.Builder.Writers.http; @@ -70,7 +70,8 @@ private static void WriteQueryParameters(CodeElement codeElement, LanguageWriter queryParams.ForEach(prop => { writer.WriteLine($"# {prop.Documentation.DescriptionTemplate}"); - writer.WriteLine($"@{prop.Name} = "); + var decodedParameterName = DecodeUrlComponent(prop.WireName); + writer.WriteLine($"@{decodedParameterName} = "); writer.WriteLine(); }); }); @@ -180,4 +181,14 @@ private static string GetDefaultValueForProperty(CodeProperty prop) }; } + /// + /// Decodes a URL string component, replacing percent-encoded characters with their decoded equivalents. + /// + /// The URL string component to decode. + /// The decoded URL string component. + private static string DecodeUrlComponent(string urlComponent) + { + return HttpUtility.UrlDecode(urlComponent); + } + } From f2f1c8ea6cdf4a046a216423590de32a34e3dc66 Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 18 Nov 2024 17:43:26 +0300 Subject: [PATCH 09/66] write all the path parameter variables --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 49 +++++++++++++++++++ .../HTTP/CodeClassDeclarationWriter.cs | 43 ++++++++++------ 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 9f2e621337..4c4cb5ad5a 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -206,9 +206,58 @@ private static void RemoveUnusedCodeElements(CodeElement element) var parentNameSpace = element.GetImmediateParentOfType(); parentNameSpace?.RemoveChildElement(element); } + else + { + // Add path variables + AddPathParameters(element); + } CrawlTree(element, RemoveUnusedCodeElements); } + private static void AddPathParameters(CodeElement element) + { + // Target RequestBuilder Classes only + if (element is not CodeClass codeClass) return; + + var parent = element.GetImmediateParentOfType().Parent; + while (parent is not null) + { + var codeIndexer = parent.GetChildElements(false) + .OfType() + .FirstOrDefault()? + .GetChildElements(false) + .OfType() + .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)); + + if (codeIndexer is not null) + { + // Retrieve all the parameters of kind CodeParameterKind.Custom + var customParameters = codeIndexer.Parameters + .Where(param => param.IsOfKind(CodeParameterKind.Custom)) + .ToList(); + + // For each parameter: + foreach (var param in customParameters) + { + // Create a new property of kind CodePropertyKind.PathParameters using the parameter and add it to the codeClass + var pathParameterProperty = new CodeProperty + { + Name = param.Name, + Kind = CodePropertyKind.PathParameters, + Type = param.Type, + Access = AccessModifier.Public, + DefaultValue = param.DefaultValue, + SerializationName = param.SerializationName, + Documentation = param.Documentation + }; + codeClass.AddProperty(pathParameterProperty); + } + } + + parent = parent.Parent?.GetImmediateParentOfType(); + } + } + private static bool IsRequestBuilderClass(CodeElement element) { return element is CodeClass code && code.IsOfKind(CodeClassKind.RequestBuilder); diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 65038dcab4..16399e5f8b 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -23,6 +23,9 @@ protected override void WriteTypeDeclaration(ClassDeclaration codeElement, Langu // Extract and write the URL template WriteUrlTemplate(codeElement, writer); + // Write path parameters + WritePathParameters(codeElement, writer); + // Write all query parameter variables WriteQueryParameters(codeElement, writer); @@ -52,6 +55,20 @@ private static void WriteUrlTemplate(CodeElement codeElement, LanguageWriter wri writer.WriteLine(); } + private static void WritePathParameters(CodeElement codeElement, LanguageWriter writer) + { + var pathParameters = codeElement.Parent? + .GetChildElements(true) + .OfType() + .Where(property => property.IsOfKind(CodePropertyKind.PathParameters)) + .ToList(); + + pathParameters?.ForEach(prop => + { + WriteHttpParameterProperty(prop, writer); + }); + } + private static void WriteQueryParameters(CodeElement codeElement, LanguageWriter writer) { var queryParameterClasses = codeElement.Parent? @@ -69,14 +86,21 @@ private static void WriteQueryParameters(CodeElement codeElement, LanguageWriter queryParams.ForEach(prop => { - writer.WriteLine($"# {prop.Documentation.DescriptionTemplate}"); - var decodedParameterName = DecodeUrlComponent(prop.WireName); - writer.WriteLine($"@{decodedParameterName} = "); - writer.WriteLine(); + WriteHttpParameterProperty(prop, writer); }); }); } + private static void WriteHttpParameterProperty(CodeProperty property, LanguageWriter writer) + { + if (!string.IsNullOrEmpty(property.Name)) + { + writer.WriteLine($"# {property.Documentation.DescriptionTemplate}"); + writer.WriteLine($"@{property.Name} = "); + writer.WriteLine(); + } + } + private static void WriteHttpMethods(CodeElement codeElement, LanguageWriter writer) { var httpMethods = codeElement.Parent? @@ -180,15 +204,4 @@ private static string GetDefaultValueForProperty(CodeProperty prop) _ => "null" }; } - - /// - /// Decodes a URL string component, replacing percent-encoded characters with their decoded equivalents. - /// - /// The URL string component to decode. - /// The decoded URL string component. - private static string DecodeUrlComponent(string urlComponent) - { - return HttpUtility.UrlDecode(urlComponent); - } - } From ec4aea7ffc04b5ebf0eccb9ab7fa83cdde565eb5 Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 18 Nov 2024 18:10:36 +0300 Subject: [PATCH 10/66] Exclude the generic pathParameters variable from being written to the output. --- .../Writers/HTTP/CodeClassDeclarationWriter.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 16399e5f8b..72ecbd1c6d 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Web; using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; namespace Kiota.Builder.Writers.http; public class CodeClassDeclarationWriter(HttpConventionService conventionService) : CodeProprietableBlockDeclarationWriter(conventionService) @@ -57,10 +58,11 @@ private static void WriteUrlTemplate(CodeElement codeElement, LanguageWriter wri private static void WritePathParameters(CodeElement codeElement, LanguageWriter writer) { + // Retrieve all the path variables except the generic path parameter named "pathParameters" var pathParameters = codeElement.Parent? .GetChildElements(true) .OfType() - .Where(property => property.IsOfKind(CodePropertyKind.PathParameters)) + .Where(property => property.IsOfKind(CodePropertyKind.PathParameters) && !property.Name.Equals("pathParameters", StringComparison.OrdinalIgnoreCase)) .ToList(); pathParameters?.ForEach(prop => @@ -96,7 +98,7 @@ private static void WriteHttpParameterProperty(CodeProperty property, LanguageWr if (!string.IsNullOrEmpty(property.Name)) { writer.WriteLine($"# {property.Documentation.DescriptionTemplate}"); - writer.WriteLine($"@{property.Name} = "); + writer.WriteLine($"@{property.Name.ToFirstCharacterLowerCase()} = "); writer.WriteLine(); } } From da3bf0562925e17b78aaeacdfc126403d4df2a1c Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 18 Nov 2024 18:55:44 +0300 Subject: [PATCH 11/66] add comments and documentation info. to Writers/HTTP/CodeClassDeclarationWriter.cs --- .../HTTP/CodeClassDeclarationWriter.cs | 121 ++++++++++++++---- 1 file changed, 94 insertions(+), 27 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 72ecbd1c6d..09b1bca791 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -12,40 +12,49 @@ protected override void WriteTypeDeclaration(ClassDeclaration codeElement, Langu ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - if (codeElement.Parent is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.RequestBuilder)) + if (codeElement.Parent is CodeClass requestBuilderClass && requestBuilderClass.IsOfKind(CodeClassKind.RequestBuilder)) { // Write short description - conventions.WriteShortDescription(codeClass, writer); + conventions.WriteShortDescription(requestBuilderClass, writer); writer.WriteLine(); // Write the baseUrl variable - WriteBaseUrl(codeClass, writer); + WriteBaseUrl(requestBuilderClass, writer); // Extract and write the URL template - WriteUrlTemplate(codeElement, writer); + WriteUrlTemplate(requestBuilderClass, writer); // Write path parameters - WritePathParameters(codeElement, writer); + WritePathParameters(requestBuilderClass, writer); // Write all query parameter variables - WriteQueryParameters(codeElement, writer); + WriteQueryParameters(requestBuilderClass, writer); // Write all HTTP methods GET, POST, PUT, DELETE e.t.c - WriteHttpMethods(codeElement, writer); + WriteHttpMethods(requestBuilderClass, writer); } } - private static void WriteBaseUrl(CodeClass codeClass, LanguageWriter writer) + /// + /// Writes the base URL for the given request builder class to the writer. + /// + /// The request builder class containing the base URL property. + /// The language writer to write the base URL to. + private static void WriteBaseUrl(CodeClass requestBuilderClass, LanguageWriter writer) { - var baseUrl = codeClass.Properties.FirstOrDefault(property => property.Name.Equals("BaseUrl", StringComparison.OrdinalIgnoreCase))?.DefaultValue; + // Retrieve the base URL property from the request builder class + var baseUrl = requestBuilderClass.Properties + .FirstOrDefault(property => property.Name.Equals("BaseUrl", StringComparison.OrdinalIgnoreCase))?.DefaultValue; + + // Write the base URL variable to the writer writer.WriteLine($"# baseUrl"); writer.WriteLine($"@baseUrl = {baseUrl}"); writer.WriteLine(); } - private static void WriteUrlTemplate(CodeElement codeElement, LanguageWriter writer) + private static void WriteUrlTemplate(CodeClass requestBuilderClass, LanguageWriter writer) { - var urlTemplateProperty = codeElement.Parent? + var urlTemplateProperty = requestBuilderClass .GetChildElements(true) .OfType() .FirstOrDefault(property => property.IsOfKind(CodePropertyKind.UrlTemplate)); @@ -56,29 +65,42 @@ private static void WriteUrlTemplate(CodeElement codeElement, LanguageWriter wri writer.WriteLine(); } - private static void WritePathParameters(CodeElement codeElement, LanguageWriter writer) + /// + /// Writes the path parameters for the given request builder class to the writer. + /// + /// The request builder class containing the path parameters. + /// The language writer to write the path parameters to. + private static void WritePathParameters(CodeClass requestBuilderClass, LanguageWriter writer) { // Retrieve all the path variables except the generic path parameter named "pathParameters" - var pathParameters = codeElement.Parent? + var pathParameters = requestBuilderClass .GetChildElements(true) .OfType() .Where(property => property.IsOfKind(CodePropertyKind.PathParameters) && !property.Name.Equals("pathParameters", StringComparison.OrdinalIgnoreCase)) .ToList(); + // Write each path parameter property pathParameters?.ForEach(prop => { WriteHttpParameterProperty(prop, writer); }); } - private static void WriteQueryParameters(CodeElement codeElement, LanguageWriter writer) + /// + /// Writes the query parameters for the given request builder class to the writer. + /// + /// The request builder class containing the query parameters. + /// The language writer to write the query parameters to. + private static void WriteQueryParameters(CodeClass requestBuilderClass, LanguageWriter writer) { - var queryParameterClasses = codeElement.Parent? + // Retrieve all the query parameter classes + var queryParameterClasses = requestBuilderClass .GetChildElements(true) .OfType() .Where(element => element.IsOfKind(CodeClassKind.QueryParameters)) .ToList(); + // Write each query parameter property queryParameterClasses?.ForEach(paramCodeClass => { var queryParams = paramCodeClass @@ -93,47 +115,81 @@ private static void WriteQueryParameters(CodeElement codeElement, LanguageWriter }); } + /// + /// Writes the HTTP parameter property to the writer. + /// + /// The property to write. + /// The language writer to write the property to. private static void WriteHttpParameterProperty(CodeProperty property, LanguageWriter writer) { if (!string.IsNullOrEmpty(property.Name)) { + // Write the property documentation as a comment writer.WriteLine($"# {property.Documentation.DescriptionTemplate}"); + + // Write the property name and an assignment placeholder writer.WriteLine($"@{property.Name.ToFirstCharacterLowerCase()} = "); + + // Write an empty line for separation writer.WriteLine(); } } - private static void WriteHttpMethods(CodeElement codeElement, LanguageWriter writer) + /// + /// Writes the HTTP methods (GET, POST, PATCH, DELETE, e.t.c) for the given request builder class to the writer. + /// + /// The request builder class containing the HTTP methods. + /// The language writer to write the HTTP methods to. + private static void WriteHttpMethods(CodeClass requestBuilderClass, LanguageWriter writer) { - var httpMethods = codeElement.Parent? + // Retrieve all the HTTP methods of kind RequestExecutor + var httpMethods = requestBuilderClass .GetChildElements(true) .OfType() .Where(element => element.IsOfKind(CodeMethodKind.RequestExecutor)) .ToList(); + // Write each HTTP method httpMethods?.ForEach(method => { + // Write the method documentation as a comment writer.WriteLine($"# {method.Documentation.DescriptionTemplate}"); - writer.WriteLine($"{method.Name.ToUpperInvariant()} {GetUrlTemplate(codeElement)}"); + // Write the method name and URL template + writer.WriteLine($"{method.Name.ToUpperInvariant()} {GetUrlTemplate(requestBuilderClass)}"); + + // Write the request body if present WriteRequestBody(method, writer); + // Write an empty line for separation writer.WriteLine(); writer.WriteLine("###"); writer.WriteLine(); }); } - private static string GetUrlTemplate(CodeElement codeElement) + /// + /// Retrieves the URL template for the given request builder class. + /// + /// The request builder class containing the URL template property. + /// The URL template as a string, or an empty string if not found. + private static string GetUrlTemplate(CodeClass requestBuilderClass) { - var urlTemplateProperty = codeElement.Parent? + // Retrieve the URL template property from the request builder class + var urlTemplateProperty = requestBuilderClass .GetChildElements(true) .OfType() .FirstOrDefault(property => property.IsOfKind(CodePropertyKind.UrlTemplate)); + // Return the URL template or an empty string if not found return urlTemplateProperty?.DefaultValue ?? string.Empty; } + /// + /// Writes the request body for the given method to the writer. + /// + /// The method containing the request body. + /// The language writer to write the request body to. private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) { // If there is a request body, write it @@ -144,7 +200,7 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) writer.WriteLine(); writer.WriteLine($"Content-Type: {method.RequestBodyContentType}"); - // loop through the properties of the request body and write a JSON object + // Loop through the properties of the request body and write a JSON object if (requestBody.Type is CodeType ct && ct.TypeDefinition is CodeClass requestBodyClass) { writer.WriteLine("{"); @@ -154,9 +210,15 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) writer.WriteLine("}"); } } - private static void WriteProperties(CodeClass codeClass, LanguageWriter writer) + + /// + /// Writes the properties of the given request body class to the writer. + /// + /// The request body class containing the properties. + /// The language writer to write the properties to. + private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter writer) { - var properties = codeClass.Properties.Where(prop => prop.IsOfKind(CodePropertyKind.Custom)).ToList(); + var properties = requestBodyClass.Properties.Where(prop => prop.IsOfKind(CodePropertyKind.Custom)).ToList(); for (int i = 0; i < properties.Count; i++) { var prop = properties[i]; @@ -188,20 +250,25 @@ private static void WriteProperties(CodeClass codeClass, LanguageWriter writer) } // If the class extends another class, write properties of the base class - if (codeClass.StartBlock.Inherits?.TypeDefinition is CodeClass baseClass) + if (requestBodyClass.StartBlock.Inherits?.TypeDefinition is CodeClass baseClass) { WriteProperties(baseClass, writer); } } - private static string GetDefaultValueForProperty(CodeProperty prop) + /// + /// Gets the default value for the given property. + /// + /// The property to get the default value for. + /// The default value as a string. + private static string GetDefaultValueForProperty(CodeProperty codeProperty) { - return prop.Type.Name switch + return codeProperty.Type.Name switch { "int" or "integer" => "0", "string" => "\"string\"", "bool" or "boolean" => "false", - _ when prop.Type is CodeType enumType && enumType.TypeDefinition is CodeEnum enumDefinition => + _ when codeProperty.Type is CodeType enumType && enumType.TypeDefinition is CodeEnum enumDefinition => enumDefinition.Options.FirstOrDefault()?.Name is string enumName ? $"\"{enumName}\"" : "null", _ => "null" }; From 3852185fd0e919a5418431a53a5d3c4c7c3f2d36 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 19 Nov 2024 18:37:47 +0300 Subject: [PATCH 12/66] Use URL Template to write actual urls --- .../HTTP/CodeClassDeclarationWriter.cs | 199 +++++++++++++----- 1 file changed, 143 insertions(+), 56 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 09b1bca791..08b84be62a 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -1,8 +1,9 @@ using System; +using System.Collections.Generic; using System.Linq; -using System.Web; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; +using Microsoft.Kiota.Abstractions; namespace Kiota.Builder.Writers.http; public class CodeClassDeclarationWriter(HttpConventionService conventionService) : CodeProprietableBlockDeclarationWriter(conventionService) @@ -12,107 +13,160 @@ protected override void WriteTypeDeclaration(ClassDeclaration codeElement, Langu ArgumentNullException.ThrowIfNull(codeElement); ArgumentNullException.ThrowIfNull(writer); - if (codeElement.Parent is CodeClass requestBuilderClass && requestBuilderClass.IsOfKind(CodeClassKind.RequestBuilder)) + if (codeElement.Parent is CodeClass requestBuilderClass && requestBuilderClass.IsOfKind(CodeClassKind.RequestBuilder) && GetUrlTemplateProperty(requestBuilderClass) is CodeProperty urlTemplateProperty) { // Write short description conventions.WriteShortDescription(requestBuilderClass, writer); writer.WriteLine(); + // Retrieve all query parameters + var queryParameters = GetAllQueryParameters(requestBuilderClass); + + // Retrieve all path parameters + var pathParameters = GetPathParameters(requestBuilderClass); + + var baseUrl = GetBaseUrl(requestBuilderClass); + // Write the baseUrl variable - WriteBaseUrl(requestBuilderClass, writer); + WriteBaseUrl(baseUrl, writer); // Extract and write the URL template - WriteUrlTemplate(requestBuilderClass, writer); + WriteUrlTemplate(urlTemplateProperty, writer); // Write path parameters - WritePathParameters(requestBuilderClass, writer); + WritePathParameters(pathParameters, writer); // Write all query parameter variables - WriteQueryParameters(requestBuilderClass, writer); + WriteQueryParameters(queryParameters, writer); // Write all HTTP methods GET, POST, PUT, DELETE e.t.c - WriteHttpMethods(requestBuilderClass, writer); + WriteHttpMethods(requestBuilderClass, writer, queryParameters, pathParameters, urlTemplateProperty, baseUrl); } } + /// + /// Retrieves all the query parameters for the given request builder class. + /// + /// The request builder class containing the query parameters. + /// A list of all query parameters. + private static List GetAllQueryParameters(CodeClass requestBuilderClass) + { + var queryParameters = new List(); + + // Retrieve all the query parameter classes + var queryParameterClasses = requestBuilderClass + .GetChildElements(true) + .OfType() + .Where(element => element.IsOfKind(CodeClassKind.QueryParameters)) + .ToList(); + + // Collect all query parameter properties into the aggregated list + queryParameterClasses?.ForEach(paramCodeClass => + { + var queryParams = paramCodeClass + .Properties + .Where(property => property.IsOfKind(CodePropertyKind.QueryParameter)) + .ToList(); + + queryParameters.AddRange(queryParams); + }); + + return queryParameters; + } + + /// + /// Retrieves all the path parameters for the given request builder class. + /// + /// The request builder class containing the path parameters. + /// A list of all path parameters, or an empty list if none are found. + private static List GetPathParameters(CodeClass requestBuilderClass) + { + // Retrieve all the path variables except the generic path parameter named "pathParameters" + var pathParameters = requestBuilderClass + .GetChildElements(true) + .OfType() + .Where(property => property.IsOfKind(CodePropertyKind.PathParameters) && !property.Name.Equals("pathParameters", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + return pathParameters ?? []; + } + /// /// Writes the base URL for the given request builder class to the writer. /// /// The request builder class containing the base URL property. /// The language writer to write the base URL to. - private static void WriteBaseUrl(CodeClass requestBuilderClass, LanguageWriter writer) + private static void WriteBaseUrl(string? baseUrl, LanguageWriter writer) { - // Retrieve the base URL property from the request builder class - var baseUrl = requestBuilderClass.Properties - .FirstOrDefault(property => property.Name.Equals("BaseUrl", StringComparison.OrdinalIgnoreCase))?.DefaultValue; - // Write the base URL variable to the writer writer.WriteLine($"# baseUrl"); writer.WriteLine($"@baseUrl = {baseUrl}"); writer.WriteLine(); } - private static void WriteUrlTemplate(CodeClass requestBuilderClass, LanguageWriter writer) + /// + /// Retrieves the base URL for the given request builder class. + /// + /// The request builder class containing the base URL property. + /// The base URL as a string, or null if not found. + private static string? GetBaseUrl(CodeClass requestBuilderClass) { - var urlTemplateProperty = requestBuilderClass + // Retrieve the base URL property from the request builder class + return requestBuilderClass.Properties + .FirstOrDefault(property => property.Name.Equals("BaseUrl", StringComparison.OrdinalIgnoreCase))?.DefaultValue; + } + + /// + /// Retrieves the URL template property for the given request builder class. + /// + /// The request builder class containing the URL template property. + /// The URL template property, or null if not found. + private static CodeProperty? GetUrlTemplateProperty(CodeClass requestBuilderClass) + { + // Retrieve the URL template property from the request builder class + return requestBuilderClass .GetChildElements(true) .OfType() .FirstOrDefault(property => property.IsOfKind(CodePropertyKind.UrlTemplate)); + } + + /// + /// Writes the URL template for the given URL template property to the writer. + /// + /// The URL template property containing the URL template. + /// The language writer to write the URL template to. + private static void WriteUrlTemplate(CodeProperty urlTemplateProperty, LanguageWriter writer) + { + // Write the URL template documentation as a comment + writer.WriteLine($"# {urlTemplateProperty.Documentation?.DescriptionTemplate}"); - var urlTemplate = urlTemplateProperty?.DefaultValue; - writer.WriteLine($"# {urlTemplateProperty?.Documentation?.DescriptionTemplate}"); - writer.WriteLine($"# {urlTemplate}"); + // Write the URL template value + writer.WriteLine($"# {urlTemplateProperty.DefaultValue}"); + + // Write an empty line for separation writer.WriteLine(); } /// /// Writes the path parameters for the given request builder class to the writer. /// - /// The request builder class containing the path parameters. + /// The list of path parameters to write. /// The language writer to write the path parameters to. - private static void WritePathParameters(CodeClass requestBuilderClass, LanguageWriter writer) + private static void WritePathParameters(List pathParameters, LanguageWriter writer) { - // Retrieve all the path variables except the generic path parameter named "pathParameters" - var pathParameters = requestBuilderClass - .GetChildElements(true) - .OfType() - .Where(property => property.IsOfKind(CodePropertyKind.PathParameters) && !property.Name.Equals("pathParameters", StringComparison.OrdinalIgnoreCase)) - .ToList(); - // Write each path parameter property - pathParameters?.ForEach(prop => - { - WriteHttpParameterProperty(prop, writer); - }); + pathParameters?.ForEach(prop => WriteHttpParameterProperty(prop, writer)); } /// /// Writes the query parameters for the given request builder class to the writer. /// - /// The request builder class containing the query parameters. + /// The list of query parameters to write. /// The language writer to write the query parameters to. - private static void WriteQueryParameters(CodeClass requestBuilderClass, LanguageWriter writer) + private static void WriteQueryParameters(List queryParameters, LanguageWriter writer) { - // Retrieve all the query parameter classes - var queryParameterClasses = requestBuilderClass - .GetChildElements(true) - .OfType() - .Where(element => element.IsOfKind(CodeClassKind.QueryParameters)) - .ToList(); - // Write each query parameter property - queryParameterClasses?.ForEach(paramCodeClass => - { - var queryParams = paramCodeClass - .Properties - .Where(property => property.IsOfKind(CodePropertyKind.QueryParameter)) - .ToList(); - - queryParams.ForEach(prop => - { - WriteHttpParameterProperty(prop, writer); - }); - }); + queryParameters.ForEach(prop => WriteHttpParameterProperty(prop, writer)); } /// @@ -140,7 +194,7 @@ private static void WriteHttpParameterProperty(CodeProperty property, LanguageWr /// /// The request builder class containing the HTTP methods. /// The language writer to write the HTTP methods to. - private static void WriteHttpMethods(CodeClass requestBuilderClass, LanguageWriter writer) + private static void WriteHttpMethods(CodeClass requestBuilderClass, LanguageWriter writer, List queryParameters, List pathParameters, CodeProperty urlTemplateProperty, string? baseUrl) { // Retrieve all the HTTP methods of kind RequestExecutor var httpMethods = requestBuilderClass @@ -155,8 +209,11 @@ private static void WriteHttpMethods(CodeClass requestBuilderClass, LanguageWrit // Write the method documentation as a comment writer.WriteLine($"# {method.Documentation.DescriptionTemplate}"); - // Write the method name and URL template - writer.WriteLine($"{method.Name.ToUpperInvariant()} {GetUrlTemplate(requestBuilderClass)}"); + // Build the actual URL string and replace all required fields(path and query) with placeholder variables + var url = BuildUrlStringFromTemplate(urlTemplateProperty.DefaultValue, queryParameters, pathParameters, baseUrl); + + // Write the http operation e.g GET, POST, PATCH, e.t.c + writer.WriteLine($"{method.Name.ToUpperInvariant()} {url} HTTP/1.1"); // Write the request body if present WriteRequestBody(method, writer); @@ -196,10 +253,11 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) var requestBody = method.Parameters.FirstOrDefault(param => param.IsOfKind(CodeParameterKind.RequestBody)); if (requestBody is null) return; - // Empty line before content type - writer.WriteLine(); writer.WriteLine($"Content-Type: {method.RequestBodyContentType}"); + // Empty line before body content + writer.WriteLine(); + // Loop through the properties of the request body and write a JSON object if (requestBody.Type is CodeType ct && ct.TypeDefinition is CodeClass requestBodyClass) { @@ -273,4 +331,33 @@ private static string GetDefaultValueForProperty(CodeProperty codeProperty) _ => "null" }; } + + private static string BuildUrlStringFromTemplate(string urlTemplateString, List queryParameters, List pathParameters, string? baseUrl) + { + // Use the provided baseUrl or default to "http://localhost/" + baseUrl ??= "http://localhost/"; + + // unquote the urlTemplate string and replace the {+baseurl} with the actual base url string + urlTemplateString = urlTemplateString.Trim('"').Replace("{+baseurl}", baseUrl, StringComparison.InvariantCultureIgnoreCase); + + // Build RequestInformation using the URL + var requestInformation = new RequestInformation() + { + UrlTemplate = urlTemplateString + }; + + queryParameters?.ForEach(param => + { + // Check if its a required parameter then add it + requestInformation.QueryParameters.Add(param.WireName, $"{{{param.Name.ToFirstCharacterLowerCase()}}}"); + }); + + pathParameters?.ForEach(param => + { + requestInformation.PathParameters.Add(param.WireName, $"{{{param.Name.ToFirstCharacterLowerCase()}}}"); + }); + + // Erase baseUrl and use the placeholder variable {baseUrl} already defined in the snippet + return requestInformation.URI.ToString().Replace(baseUrl, "{baseUrl}", StringComparison.InvariantCultureIgnoreCase); + } } From 7508ebd99fa95f1096d608cf8d325a9a626077f9 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 21 Nov 2024 14:56:58 +0300 Subject: [PATCH 13/66] use double curly braces for all variables --- .../HTTP/CodeClassDeclarationWriter.cs | 61 ++++++++++++------- .../Writers/HTTP/HttpConventionService.cs | 4 +- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 08b84be62a..8e1ee9b464 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -8,6 +8,8 @@ namespace Kiota.Builder.Writers.http; public class CodeClassDeclarationWriter(HttpConventionService conventionService) : CodeProprietableBlockDeclarationWriter(conventionService) { + private const string BaseUrlPropertyName = "url"; + protected override void WriteTypeDeclaration(ClassDeclaration codeElement, LanguageWriter writer) { ArgumentNullException.ThrowIfNull(codeElement); @@ -99,8 +101,8 @@ private static List GetPathParameters(CodeClass requestBuilderClas private static void WriteBaseUrl(string? baseUrl, LanguageWriter writer) { // Write the base URL variable to the writer - writer.WriteLine($"# baseUrl"); - writer.WriteLine($"@baseUrl = {baseUrl}"); + writer.WriteLine($"# Base url for the server/host"); + writer.WriteLine($"@{BaseUrlPropertyName} = {baseUrl}"); writer.WriteLine(); } @@ -190,39 +192,57 @@ private static void WriteHttpParameterProperty(CodeProperty property, LanguageWr } /// - /// Writes the HTTP methods (GET, POST, PATCH, DELETE, e.t.c) for the given request builder class to the writer. + /// Writes the HTTP methods (GET, POST, PATCH, DELETE, etc.) for the given request builder class to the writer. /// /// The request builder class containing the HTTP methods. /// The language writer to write the HTTP methods to. + /// The list of query parameters. + /// The list of path parameters. + /// The URL template property containing the URL template. + /// The base URL. private static void WriteHttpMethods(CodeClass requestBuilderClass, LanguageWriter writer, List queryParameters, List pathParameters, CodeProperty urlTemplateProperty, string? baseUrl) { // Retrieve all the HTTP methods of kind RequestExecutor - var httpMethods = requestBuilderClass - .GetChildElements(true) - .OfType() - .Where(element => element.IsOfKind(CodeMethodKind.RequestExecutor)) - .ToList(); + var httpMethods = GetHttpMethods(requestBuilderClass); - // Write each HTTP method - httpMethods?.ForEach(method => + for (int i = 0; i < httpMethods.Count; i++) { + var method = httpMethods[i]; + // Write the method documentation as a comment writer.WriteLine($"# {method.Documentation.DescriptionTemplate}"); - // Build the actual URL string and replace all required fields(path and query) with placeholder variables + // Build the actual URL string and replace all required fields (path and query) with placeholder variables var url = BuildUrlStringFromTemplate(urlTemplateProperty.DefaultValue, queryParameters, pathParameters, baseUrl); - // Write the http operation e.g GET, POST, PATCH, e.t.c + // Write the HTTP operation (e.g., GET, POST, PATCH, etc.) writer.WriteLine($"{method.Name.ToUpperInvariant()} {url} HTTP/1.1"); // Write the request body if present WriteRequestBody(method, writer); - // Write an empty line for separation - writer.WriteLine(); - writer.WriteLine("###"); - writer.WriteLine(); - }); + // Write an empty line for separation if there are more items that follow + if (i < httpMethods.Count - 1) + { + writer.WriteLine(); + writer.WriteLine("###"); + writer.WriteLine(); + } + } + } + + /// + /// Retrieves all the HTTP methods of kind RequestExecutor for the given request builder class. + /// + /// The request builder class containing the HTTP methods. + /// A list of HTTP methods of kind RequestExecutor. + private static List GetHttpMethods(CodeClass requestBuilderClass) + { + return requestBuilderClass + .GetChildElements(true) + .OfType() + .Where(element => element.IsOfKind(CodeMethodKind.RequestExecutor)) + .ToList(); } /// @@ -348,16 +368,15 @@ private static string BuildUrlStringFromTemplate(string urlTemplateString, List< queryParameters?.ForEach(param => { - // Check if its a required parameter then add it - requestInformation.QueryParameters.Add(param.WireName, $"{{{param.Name.ToFirstCharacterLowerCase()}}}"); + requestInformation.QueryParameters.Add(param.WireName, $"{{{{{param.Name.ToFirstCharacterLowerCase()}}}}}"); }); pathParameters?.ForEach(param => { - requestInformation.PathParameters.Add(param.WireName, $"{{{param.Name.ToFirstCharacterLowerCase()}}}"); + requestInformation.PathParameters.Add(param.WireName, $"{{{{{param.Name.ToFirstCharacterLowerCase()}}}}}"); }); // Erase baseUrl and use the placeholder variable {baseUrl} already defined in the snippet - return requestInformation.URI.ToString().Replace(baseUrl, "{baseUrl}", StringComparison.InvariantCultureIgnoreCase); + return requestInformation.URI.ToString().Replace(baseUrl, $"{{{{{BaseUrlPropertyName}}}}}", StringComparison.InvariantCultureIgnoreCase); } } diff --git a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs index e4c14b91a6..4098fe4d40 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs @@ -27,7 +27,7 @@ public override bool WriteShortDescription(IDocumentedElement element, LanguageW if (element is not CodeElement codeElement) return false; var description = element.Documentation.GetDescription(type => GetTypeString(type, codeElement)); - writer.WriteLine($"{DocCommentPrefix}{prefix}{description}{prefix}"); + writer.WriteLine($"{DocCommentPrefix} {prefix}{description}{prefix}"); return true; } @@ -62,7 +62,7 @@ private string GetNamesInUseByNamespaceSegments(CodeNamespace typeNS, CodeElemen { NamespaceDifferentialTrackerState.Same => string.Empty, NamespaceDifferentialTrackerState.Downwards => $"{string.Join('.', diffResult.DownwardsSegments)}.", - NamespaceDifferentialTrackerState.Upwards => string.Empty, //TODO + NamespaceDifferentialTrackerState.Upwards => string.Empty, NamespaceDifferentialTrackerState.UpwardsAndThenDownwards => $"{typeNS.Name}.", _ => throw new NotImplementedException(), }; From 41345d56249551ed6192f948675da472d70255ad Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 21 Nov 2024 15:24:16 +0300 Subject: [PATCH 14/66] add unit tests for HttpPathSegmenter --- .../PathSegmenters/HttpPathSegmenterTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs diff --git a/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs b/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs new file mode 100644 index 0000000000..779e19e310 --- /dev/null +++ b/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs @@ -0,0 +1,34 @@ +using Kiota.Builder.CodeDOM; +using Kiota.Builder.PathSegmenters; +using Xunit; + +namespace Kiota.Builder.Tests.PathSegmenters +{ + public class HttpPathSegmenterTests + { + private readonly HttpPathSegmenter segmenter; + + public HttpPathSegmenterTests() + { + segmenter = new HttpPathSegmenter("D:\\source\\repos\\kiota-sample", "client"); + } + + [Fact] + public void HttpPathSegmenterGeneratesCorrectFileName() + { + var fileName = segmenter.NormalizeFileName(new CodeClass + { + Name = "testClass" + }); + Assert.Equal("TestClass", fileName);// the file name should be Proper case + } + + [Fact] + public void HttpPathSegmenterGeneratesNamespaceFolderName() + { + var namespaceName = "microsoft.Graph"; + var normalizedNamespace = segmenter.NormalizeNamespaceSegment(namespaceName); + Assert.Equal("Microsoft.Graph", normalizedNamespace);// the first character is upper case + } + } +} From cfabf372c5206947f43fc88b0664cb9f5dded908 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 21 Nov 2024 18:48:27 +0300 Subject: [PATCH 15/66] unit tests for CodeEnum writer --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 125 +----------------- .../Writers/HTTP/CodeEnumWriterTests.cs | 63 +++++++++ 2 files changed, 64 insertions(+), 124 deletions(-) create mode 100644 tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 4c4cb5ad5a..3b23a81703 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -8,9 +8,8 @@ using Kiota.Builder.Extensions; namespace Kiota.Builder.Refiners; -public class HttpRefiner : CommonLanguageRefiner +public class HttpRefiner(GenerationConfiguration configuration) : CommonLanguageRefiner(configuration) { - public HttpRefiner(GenerationConfiguration configuration) : base(configuration) { } public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken cancellationToken) { return Task.Run(() => @@ -38,17 +37,8 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken SetBaseUrlForRequestBuilderMethods(generatedCode, GetBaseUrl(generatedCode)); // Remove unused code from the DOM e.g Models, BarrelInitializers, e.t.c RemoveUnusedCodeElements(generatedCode); - CorrectCoreType( - generatedCode, - CorrectMethodType, - CorrectPropertyType, - CorrectImplements); }, cancellationToken); } - private static void CorrectImplements(ProprietableBlockDeclaration block) - { - block.ReplaceImplementByName(KiotaBuilder.AdditionalHolderInterface, "AdditionalDataHolder"); - } private string? GetBaseUrl(CodeElement element) { @@ -61,119 +51,6 @@ private static void CorrectImplements(ProprietableBlockDeclaration block) .BaseUrl; } - private static void CorrectMethodType(CodeMethod currentMethod) - { - var parentClass = currentMethod.Parent as CodeClass; - if (currentMethod.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator)) - { - if (currentMethod.IsOfKind(CodeMethodKind.RequestExecutor)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("IResponseHandler", StringComparison.Ordinal)).ToList().ForEach(x => - { - x.Type.Name = "ResponseHandler"; - x.Type.IsNullable = false; //no pointers - }); - else if (currentMethod.IsOfKind(CodeMethodKind.RequestGenerator)) - currentMethod.ReturnType.IsNullable = true; - } - else if (currentMethod.IsOfKind(CodeMethodKind.Serializer)) - currentMethod.Parameters.Where(x => x.Type.Name.Equals("ISerializationWriter", StringComparison.Ordinal)).ToList().ForEach(x => x.Type.Name = "SerializationWriter"); - else if (currentMethod.IsOfKind(CodeMethodKind.Deserializer)) - { - currentMethod.ReturnType.Name = "[String:FieldDeserializer][String:FieldDeserializer]"; - currentMethod.Name = "getFieldDeserializers"; - } - else if (currentMethod.IsOfKind(CodeMethodKind.ClientConstructor, CodeMethodKind.Constructor, CodeMethodKind.RawUrlConstructor)) - { - var rawUrlParam = currentMethod.Parameters.OfKind(CodeParameterKind.RawUrl); - if (rawUrlParam != null) - rawUrlParam.Type.IsNullable = false; - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.RequestAdapter)) - .Where(x => x.Type.Name.StartsWith('I')) - .ToList() - .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" - } - else if (currentMethod.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility, CodeMethodKind.RequestBuilderWithParameters, CodeMethodKind.RequestBuilderBackwardCompatibility, CodeMethodKind.Factory)) - { - currentMethod.ReturnType.IsNullable = true; - if (currentMethod.Parameters.OfKind(CodeParameterKind.ParseNode) is CodeParameter parseNodeParam) - { - parseNodeParam.Type.Name = parseNodeParam.Type.Name[1..]; - parseNodeParam.Type.IsNullable = false; - } - if (currentMethod.IsOfKind(CodeMethodKind.Factory)) - currentMethod.ReturnType = new CodeType { Name = "Parsable", IsNullable = false, IsExternal = true }; - } - CorrectCoreTypes(parentClass, DateTypesReplacements, currentMethod.Parameters - .Select(x => x.Type) - .Union(new[] { currentMethod.ReturnType }) - .ToArray()); - } - private static readonly Dictionary DateTypesReplacements = new(StringComparer.OrdinalIgnoreCase) { - {"DateTimeOffset", ("Date", new CodeUsing { - Name = "Date", - Declaration = new CodeType { - Name = "Foundation", - IsExternal = true, - }, - })}, - {"TimeSpan", ("Date", new CodeUsing { - Name = "Date", - Declaration = new CodeType { - Name = "Foundation", - IsExternal = true, - }, - })}, - {"DateOnly", ("Date", new CodeUsing { - Name = "Date", - Declaration = new CodeType { - Name = "Foundation", - IsExternal = true, - }, - })}, - {"TimeOnly", ("Date", new CodeUsing { - Name = "Date", - Declaration = new CodeType { - Name = "Foundation", - IsExternal = true, - }, - })}, - }; - private static void CorrectPropertyType(CodeProperty currentProperty) - { - if (currentProperty.Type != null) - { - if (currentProperty.IsOfKind(CodePropertyKind.RequestAdapter)) - { - currentProperty.Type.IsNullable = true; - currentProperty.Type.Name = "RequestAdapter"; - } - else if (currentProperty.IsOfKind(CodePropertyKind.BackingStore)) - currentProperty.Type.Name = currentProperty.Type.Name[1..]; // removing the "I" - else if (currentProperty.IsOfKind(CodePropertyKind.AdditionalData)) - { - currentProperty.Type.IsNullable = false; - currentProperty.Type.Name = "[String:Any]"; - currentProperty.DefaultValue = $"{currentProperty.Type.Name}()"; - } - else if (currentProperty.IsOfKind(CodePropertyKind.PathParameters)) - { - currentProperty.Type.IsNullable = true; - currentProperty.Type.Name = "[String:String]"; - if (!string.IsNullOrEmpty(currentProperty.DefaultValue)) - currentProperty.DefaultValue = $"{currentProperty.Type.Name}()"; - } - else if (currentProperty.IsOfKind(CodePropertyKind.Options)) - { - currentProperty.Type.IsNullable = false; - currentProperty.Type.Name = "RequestOption"; - currentProperty.Type.CollectionKind = CodeTypeBase.CodeTypeCollectionKind.Array; - } - else if (currentProperty.IsOfKind(CodePropertyKind.QueryParameter) && currentProperty.Parent is CodeClass parentClass) - currentProperty.Type.Name = $"{parentClass.Name}{currentProperty.Type.Name}"; - CorrectCoreTypes(currentProperty.Parent as CodeClass, DateTypesReplacements, currentProperty.Type); - } - } - private static void CapitalizeNamespacesFirstLetters(CodeElement current) { if (current is CodeNamespace currentNamespace) diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs new file mode 100644 index 0000000000..657a13d90a --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Linq; + +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Extensions; +using Kiota.Builder.Writers; +using Kiota.Builder.Writers.http; +using Xunit; + +namespace Kiota.Builder.Tests.Writers.Http; +public sealed class CodeEnumWriterTests : IDisposable +{ + private const string DefaultPath = "./"; + private const string DefaultName = "name"; + private readonly StringWriter tw; + private readonly LanguageWriter writer; + private readonly CodeEnum currentEnum; + private const string EnumName = "someEnum"; + private readonly CodeEnumWriter codeEnumWriter; + public CodeEnumWriterTests() + { + writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.HTTP, DefaultPath, DefaultName); + codeEnumWriter = new CodeEnumWriter(new HttpConventionService("foo")); + tw = new StringWriter(); + writer.SetTextWriter(tw); + var root = CodeNamespace.InitRootNamespace(); + currentEnum = root.AddEnum(new CodeEnum + { + Name = EnumName, + }).First(); + if (CodeConstant.FromCodeEnum(currentEnum) is CodeConstant constant) + { + currentEnum.CodeEnumObject = constant; + root.AddConstant(constant); + } + } + public void Dispose() + { + tw?.Dispose(); + GC.SuppressFinalize(this); + } + [Fact] + public void WriteCodeElement_ThrowsException_WhenCodeElementIsNull() + { + Assert.Throws(() => codeEnumWriter.WriteCodeElement(null, writer)); + } + [Fact] + public void WriteCodeElement_ThrowsException_WhenWriterIsNull() + { + var codeElement = new CodeEnum(); + Assert.Throws(() => codeEnumWriter.WriteCodeElement(codeElement, null)); + } + [Fact] + public void SkipsEnum() + { + const string optionName = "option1"; + currentEnum.AddOption(new CodeEnumOption { Name = optionName }); + codeEnumWriter.WriteCodeElement(currentEnum, writer); + var result = tw.ToString(); + Assert.True(string.IsNullOrEmpty(result)); + } +} From cfbbefe55e6bc4e201939c6b92dc6a1d5dcab8b1 Mon Sep 17 00:00:00 2001 From: koros Date: Fri, 22 Nov 2024 12:30:37 +0300 Subject: [PATCH 16/66] format code --- .../PathSegmenters/HttpPathSegmenterTests.cs | 2 +- tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs b/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs index 779e19e310..b04f9469ba 100644 --- a/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs +++ b/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs @@ -1,4 +1,4 @@ -using Kiota.Builder.CodeDOM; +using Kiota.Builder.CodeDOM; using Kiota.Builder.PathSegmenters; using Xunit; diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs index 657a13d90a..d2ab78db4b 100644 --- a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs @@ -1,9 +1,8 @@ -using System; +using System; using System.IO; using System.Linq; using Kiota.Builder.CodeDOM; -using Kiota.Builder.Extensions; using Kiota.Builder.Writers; using Kiota.Builder.Writers.http; using Xunit; From d9f758456cddf4b16d08285c9f096fa963518095 Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 25 Nov 2024 12:44:18 +0300 Subject: [PATCH 17/66] remove more unnecessary code from the HttpConventionService --- .../Writers/HTTP/HttpConventionService.cs | 41 +------------------ src/Kiota.Builder/Writers/HTTP/HttpWriter.cs | 2 +- .../Writers/HTTP/CodeEnumWriterTests.cs | 2 +- 3 files changed, 4 insertions(+), 41 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs index 4098fe4d40..52a29876ce 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; @@ -8,10 +6,8 @@ namespace Kiota.Builder.Writers.http; public class HttpConventionService : CommonLanguageConventionService { - public HttpConventionService(string clientNamespaceName) + public HttpConventionService() { - ArgumentException.ThrowIfNullOrEmpty(clientNamespaceName); - this.clientNamespaceName = clientNamespaceName; } public override string StreamTypeName => "stream"; public override string VoidTypeName => "void"; @@ -40,38 +36,12 @@ public override string GetAccessModifier(AccessModifier access) _ => "private", }; } -#pragma warning disable CA1822 // Method should be static - internal void AddRequestBuilderBody(CodeClass parentClass, string returnType, LanguageWriter writer, string? urlTemplateVarName = default, string? prefix = default, IEnumerable? pathParameters = default) - { - if (parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProp && - parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProp) - { - var pathParametersSuffix = !(pathParameters?.Any() ?? false) ? string.Empty : $", {string.Join(", ", pathParameters.Select(static x => x.Name.ToFirstCharacterLowerCase()))}"; - var urlTplRef = urlTemplateVarName ?? pathParametersProp.Name.ToFirstCharacterUpperCase(); - writer.WriteLine($"{prefix}new {returnType}({urlTplRef}, {requestAdapterProp.Name.ToFirstCharacterUpperCase()}{pathParametersSuffix});"); - } - } public override string TempDictionaryVarName => "urlTplParams"; -#pragma warning restore CA1822 // Method should be static - private readonly string clientNamespaceName; - private string GetNamesInUseByNamespaceSegments(CodeNamespace typeNS, CodeElement currentElement) - { - var currentNS = currentElement.GetImmediateParentOfType(); - var diffResult = currentNS.GetDifferential(typeNS, clientNamespaceName); - return diffResult.State switch - { - NamespaceDifferentialTrackerState.Same => string.Empty, - NamespaceDifferentialTrackerState.Downwards => $"{string.Join('.', diffResult.DownwardsSegments)}.", - NamespaceDifferentialTrackerState.Upwards => string.Empty, - NamespaceDifferentialTrackerState.UpwardsAndThenDownwards => $"{typeNS.Name}.", - _ => throw new NotImplementedException(), - }; - } public override string GetTypeString(CodeTypeBase code, CodeElement targetElement, bool includeCollectionInformation = true, LanguageWriter? writer = null) { if (code is CodeType currentType) { - var typeName = TranslateTypeAndAvoidUsingNamespaceSegmentNames(currentType, targetElement); + var typeName = TranslateType(currentType); var nullableSuffix = code.IsNullable ? NullableMarkerAsString : string.Empty; var collectionPrefix = currentType.IsCollection && includeCollectionInformation ? "[" : string.Empty; var collectionSuffix = currentType.IsCollection && includeCollectionInformation ? $"]{nullableSuffix}" : string.Empty; @@ -85,13 +55,6 @@ public override string GetTypeString(CodeTypeBase code, CodeElement targetElemen throw new InvalidOperationException($"type of type {code?.GetType()} is unknown"); } - private string TranslateTypeAndAvoidUsingNamespaceSegmentNames(CodeType currentType, CodeElement targetElement) - { - var typeName = TranslateType(currentType); - if (currentType.TypeDefinition != null) - return $"{GetNamesInUseByNamespaceSegments(currentType.TypeDefinition.GetImmediateParentOfType(), targetElement)}{typeName}"; - return typeName; - } public override string TranslateType(CodeType type) { return type?.Name switch diff --git a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs index 23756c9be4..621c97d908 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs @@ -7,7 +7,7 @@ public class HttpWriter : LanguageWriter public HttpWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new HttpPathSegmenter(rootPath, clientNamespaceName); - var conventionService = new HttpConventionService(clientNamespaceName); + var conventionService = new HttpConventionService(); AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeBlockEndWriter()); AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs index d2ab78db4b..2b4ed4f21b 100644 --- a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs @@ -20,7 +20,7 @@ public sealed class CodeEnumWriterTests : IDisposable public CodeEnumWriterTests() { writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.HTTP, DefaultPath, DefaultName); - codeEnumWriter = new CodeEnumWriter(new HttpConventionService("foo")); + codeEnumWriter = new CodeEnumWriter(new HttpConventionService()); tw = new StringWriter(); writer.SetTextWriter(tw); var root = CodeNamespace.InitRootNamespace(); From 69bac1f0ec4f6c4f26b5f1ecd2d6419ba01ff83d Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 25 Nov 2024 12:59:03 +0300 Subject: [PATCH 18/66] rename http namespace to use proper casing --- src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs | 2 +- src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs | 2 +- src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs | 2 +- src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs | 2 +- src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs | 2 +- src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs | 2 +- .../Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs | 2 +- src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs | 2 +- src/Kiota.Builder/Writers/HTTP/HttpWriter.cs | 2 +- src/Kiota.Builder/Writers/LanguageWriter.cs | 2 +- tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs index c7e384da90..c59fd59d9c 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs @@ -1,7 +1,7 @@ using System; using Kiota.Builder.CodeDOM; -namespace Kiota.Builder.Writers.http; +namespace Kiota.Builder.Writers.Http; public class CodeBlockEndWriter : ICodeElementWriter { public void WriteCodeElement(BlockEnd codeElement, LanguageWriter writer) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 8e1ee9b464..47fe800a94 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -5,7 +5,7 @@ using Kiota.Builder.Extensions; using Microsoft.Kiota.Abstractions; -namespace Kiota.Builder.Writers.http; +namespace Kiota.Builder.Writers.Http; public class CodeClassDeclarationWriter(HttpConventionService conventionService) : CodeProprietableBlockDeclarationWriter(conventionService) { private const string BaseUrlPropertyName = "url"; diff --git a/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs index 433643449f..2c0a14d506 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs @@ -4,7 +4,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; -namespace Kiota.Builder.Writers.http; +namespace Kiota.Builder.Writers.Http; public class CodeEnumWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) { public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter writer) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs index b894ef9bbd..4098a5cbb5 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs @@ -2,7 +2,7 @@ using System.Linq; using Kiota.Builder.CodeDOM; -namespace Kiota.Builder.Writers.http; +namespace Kiota.Builder.Writers.Http; public class CodeMethodWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) { public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs index 205998912e..47b679b029 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs @@ -3,7 +3,7 @@ using Kiota.Builder.CodeDOM; -namespace Kiota.Builder.Writers.http; +namespace Kiota.Builder.Writers.Http; public class CodeNamespaceWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) { public override void WriteCodeElement(CodeNamespace codeElement, LanguageWriter writer) diff --git a/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs index 92a473204c..09cfd734ba 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs @@ -2,7 +2,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; -namespace Kiota.Builder.Writers.http; +namespace Kiota.Builder.Writers.Http; public class CodePropertyWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) { public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter writer) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs index bd752c127c..39dfd0d731 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeProprietableBlockDeclarationWriter.cs @@ -3,7 +3,7 @@ using Kiota.Builder.CodeDOM; -namespace Kiota.Builder.Writers.http; +namespace Kiota.Builder.Writers.Http; public abstract class CodeProprietableBlockDeclarationWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) where T : ProprietableBlockDeclaration diff --git a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs index 52a29876ce..6e41f8c8c7 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs @@ -3,7 +3,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; -namespace Kiota.Builder.Writers.http; +namespace Kiota.Builder.Writers.Http; public class HttpConventionService : CommonLanguageConventionService { public HttpConventionService() diff --git a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs index 621c97d908..14efff58bd 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs @@ -1,6 +1,6 @@ using Kiota.Builder.PathSegmenters; -namespace Kiota.Builder.Writers.http; +namespace Kiota.Builder.Writers.Http; public class HttpWriter : LanguageWriter { diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index de88cf46b2..87149f87ba 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -8,7 +8,7 @@ using Kiota.Builder.Writers.Cli; using Kiota.Builder.Writers.CSharp; using Kiota.Builder.Writers.Go; -using Kiota.Builder.Writers.http; +using Kiota.Builder.Writers.Http; using Kiota.Builder.Writers.Java; using Kiota.Builder.Writers.Php; using Kiota.Builder.Writers.Python; diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs index 2b4ed4f21b..3fb970fb89 100644 --- a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs @@ -4,7 +4,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Writers; -using Kiota.Builder.Writers.http; +using Kiota.Builder.Writers.Http; using Xunit; namespace Kiota.Builder.Tests.Writers.Http; From fc39c893e0d8b31016cddc001e852099e258b91c Mon Sep 17 00:00:00 2001 From: koros Date: Mon, 25 Nov 2024 16:33:46 +0300 Subject: [PATCH 19/66] remove unused method --- .../Writers/HTTP/CodeClassDeclarationWriter.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 47fe800a94..0f44852e12 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -245,23 +245,6 @@ private static List GetHttpMethods(CodeClass requestBuilderClass) .ToList(); } - /// - /// Retrieves the URL template for the given request builder class. - /// - /// The request builder class containing the URL template property. - /// The URL template as a string, or an empty string if not found. - private static string GetUrlTemplate(CodeClass requestBuilderClass) - { - // Retrieve the URL template property from the request builder class - var urlTemplateProperty = requestBuilderClass - .GetChildElements(true) - .OfType() - .FirstOrDefault(property => property.IsOfKind(CodePropertyKind.UrlTemplate)); - - // Return the URL template or an empty string if not found - return urlTemplateProperty?.DefaultValue ?? string.Empty; - } - /// /// Writes the request body for the given method to the writer. /// From 2ade0bda539623c57c647c242f4597023033734c Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 26 Nov 2024 15:25:22 +0300 Subject: [PATCH 20/66] Update HTTP Reserved Names Provider with Additional Reserved Keywords --- .../Refiners/HttpReservedNamesProvider.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs b/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs index 4ce7599837..62875fb776 100644 --- a/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs +++ b/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs @@ -5,8 +5,15 @@ namespace Kiota.Builder.Refiners; public class HttpReservedNamesProvider : IReservedNamesProvider { private readonly Lazy> _reservedNames = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { - "any" - // TODO (HTTP) add full list + "GET", + "POST", + "PUT", + "DELETE", + "PATCH", + "OPTIONS", + "HEAD", + "CONNECT", + "TRACE" }); public HashSet ReservedNames => _reservedNames.Value; } From 0fa1c0de68bc48a43df00d268a16a260d7eebfcf Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 27 Nov 2024 12:26:19 +0300 Subject: [PATCH 21/66] remove reserved names provider; add more unit tests --- .../Refiners/HttpReservedNamesProvider.cs | 12 +-- .../HTTP/CodeClassDeclarationWriterTests.cs | 93 +++++++++++++++++++ 2 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs diff --git a/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs b/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs index 62875fb776..df60e5ee20 100644 --- a/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs +++ b/src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs @@ -4,16 +4,8 @@ namespace Kiota.Builder.Refiners; public class HttpReservedNamesProvider : IReservedNamesProvider { - private readonly Lazy> _reservedNames = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) { - "GET", - "POST", - "PUT", - "DELETE", - "PATCH", - "OPTIONS", - "HEAD", - "CONNECT", - "TRACE" + private readonly Lazy> _reservedNames = new(() => new HashSet(StringComparer.OrdinalIgnoreCase) + { }); public HashSet ReservedNames => _reservedNames.Value; } diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs new file mode 100644 index 0000000000..d5a1d5cb83 --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Configuration; +using Kiota.Builder.Extensions; +using Kiota.Builder.Refiners; +using Kiota.Builder.Tests.OpenApiSampleFiles; +using Kiota.Builder.Writers; +using Microsoft.Extensions.Logging; +using Moq; +using Xunit; +using static Kiota.Builder.Refiners.HttpRefiner; + +namespace Kiota.Builder.Tests.Writers.Http; +public sealed class CodeClassDeclarationWriterTests : IDisposable +{ + private const string DefaultPath = "./"; + private const string DefaultName = "name"; + private readonly StringWriter tw; + private readonly LanguageWriter writer; + private readonly CodeNamespace root; + + public CodeClassDeclarationWriterTests() + { + writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.HTTP, DefaultPath, DefaultName); + tw = new StringWriter(); + writer.SetTextWriter(tw); + root = CodeNamespace.InitRootNamespace(); + } + public void Dispose() + { + tw?.Dispose(); + GC.SuppressFinalize(this); + } + + [Fact] + public async Task TestWriteTypeDeclaration() + { + var codeClass = new CodeClass + { + Name = "TestClass", + Kind = CodeClassKind.RequestBuilder + }; + var urlTemplateProperty = new CodeProperty + { + Name = "urlTemplate", + Kind = CodePropertyKind.UrlTemplate, + DefaultValue = "\"https://example.com/{id}\"", + Type = new CodeType + { + Name = "string", + IsExternal = true + }, + }; + codeClass.AddProperty(urlTemplateProperty); + + // Add a new property named BaseUrl and set its value to the baseUrl string + var baseUrlProperty = new CodeProperty + { + Name = "BaseUrl", + Kind = CodePropertyKind.Custom, + Access = AccessModifier.Private, + DefaultValue = "https://example.com", + Type = new CodeType { Name = "string", IsExternal = true } + }; + codeClass.AddProperty(baseUrlProperty); + + var method = new CodeMethod + { + Name = "get", + Kind = CodeMethodKind.RequestExecutor, + Documentation = new CodeDocumentation { DescriptionTemplate = "GET method" }, + ReturnType = new CodeType { Name = "void" } + }; + + codeClass.AddMethod(method); + + root.AddClass(codeClass); + + await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.HTTP }, root); + + writer.Write(codeClass.StartBlock); + var result = tw.ToString(); + + Assert.Contains("# Base url for the server/host", result); + Assert.Contains("@url = https://example.com", result); + } +} From 106cbaeba6ea410be61399028b437a03d1557f89 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 27 Nov 2024 15:02:10 +0300 Subject: [PATCH 22/66] format code --- .../Writers/HTTP/CodeClassDeclarationWriterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs index d5a1d5cb83..836f3e5a9b 100644 --- a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.IO; From 41dbd772a9a5cef565acbc18f0f2a7679da32c28 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 27 Nov 2024 15:07:14 +0300 Subject: [PATCH 23/66] reset .vscode/launch.json --- .vscode/launch.json | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 11f7bb649d..f4bcd561b3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,27 +1,6 @@ { "version": "0.2.0", "configurations": [ - { - "name": "Launch HTTP", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - "program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll", - "args": [ - "generate", - "--openapi", - "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/Mail.yml", - "--language", - "HTTP", - "-o", - "${workspaceFolder}/samples/msgraph-mail/HTTP/src", - "-n", - "graphtypescriptv4.utilities" - ], - "cwd": "${workspaceFolder}/src/kiota", - "console": "internalConsole", - "stopAtEntry": false - }, { "name": "Launch TypeScript", "type": "coreclr", From 7d307133294798e2dc71db9dcfde2ffb29f3bbb4 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 27 Nov 2024 20:28:12 +0300 Subject: [PATCH 24/66] add more unit tests --- .../HTTP/CodeClassDeclarationWriterTests.cs | 138 +++++++++++++++++- 1 file changed, 136 insertions(+), 2 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs index 836f3e5a9b..adb79e9105 100644 --- a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs @@ -39,7 +39,7 @@ public void Dispose() } [Fact] - public async Task TestWriteTypeDeclaration() + public async Task WritesBaseUrlProperty() { var codeClass = new CodeClass { @@ -59,7 +59,7 @@ public async Task TestWriteTypeDeclaration() }; codeClass.AddProperty(urlTemplateProperty); - // Add a new property named BaseUrl and set its value to the baseUrl string + // Add base url property var baseUrlProperty = new CodeProperty { Name = "BaseUrl", @@ -90,4 +90,138 @@ public async Task TestWriteTypeDeclaration() Assert.Contains("# Base url for the server/host", result); Assert.Contains("@url = https://example.com", result); } + + [Fact] + public async Task WritesRequestExecutorMethods() + { + var codeClass = new CodeClass + { + Name = "TestClass", + Kind = CodeClassKind.RequestBuilder + }; + var urlTemplateProperty = new CodeProperty + { + Name = "urlTemplate", + Kind = CodePropertyKind.UrlTemplate, + DefaultValue = "\"{+baseurl}/posts\"", + Type = new CodeType + { + Name = "string", + IsExternal = true + }, + Documentation = new CodeDocumentation + { + DescriptionTemplate = "The URL template for the request." + } + }; + codeClass.AddProperty(urlTemplateProperty); + + // Add base url property + var baseUrlProperty = new CodeProperty + { + Name = "BaseUrl", + Kind = CodePropertyKind.Custom, + Access = AccessModifier.Private, + DefaultValue = "https://example.com", + Type = new CodeType { Name = "string", IsExternal = true } + }; + codeClass.AddProperty(baseUrlProperty); + + var method = new CodeMethod + { + Name = "get", + Kind = CodeMethodKind.RequestExecutor, + Documentation = new CodeDocumentation { DescriptionTemplate = "GET method" }, + ReturnType = new CodeType { Name = "void" } + }; + codeClass.AddMethod(method); + + var postMethod = new CodeMethod + { + Name = "post", + Kind = CodeMethodKind.RequestExecutor, + Documentation = new CodeDocumentation { DescriptionTemplate = "Post method" }, + ReturnType = new CodeType { Name = "void" }, + RequestBodyContentType = "application/json" + }; + + + var typeDefinition = new CodeClass + { + Name = "PostParameter", + }; + + var properties = new List + { + new() { + Name = "body", + Kind = CodePropertyKind.Custom, + Type = new CodeType { Name = "string", IsExternal = true } + }, + new() { + Name = "id", + Kind = CodePropertyKind.Custom, + Type = new CodeType { Name = "int", IsExternal = true } + }, + new() { + Name = "title", + Kind = CodePropertyKind.Custom, + Type = new CodeType { Name = "string", IsExternal = true } + }, + new() { + Name = "userId", + Kind = CodePropertyKind.Custom, + Type = new CodeType { Name = "int", IsExternal = true } + } + }; + + typeDefinition.AddProperty(properties.ToArray()); + + // Define the parameter with the specified properties + var postParameter = new CodeParameter + { + Name = "postParameter", + Kind = CodeParameterKind.RequestBody, + Type = new CodeType + { + Name = "PostParameter", + TypeDefinition = typeDefinition + } + }; + + // Add the parameter to the post method + postMethod.AddParameter(postParameter); + + codeClass.AddMethod(postMethod); + + var patchMethod = new CodeMethod + { + Name = "patch", + Kind = CodeMethodKind.RequestExecutor, + Documentation = new CodeDocumentation { DescriptionTemplate = "Patch method" }, + ReturnType = new CodeType { Name = "void" } + }; + codeClass.AddMethod(patchMethod); + + root.AddClass(codeClass); + + await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.HTTP }, root); + + writer.Write(codeClass.StartBlock); + var result = tw.ToString(); + + // Check HTTP operations + Assert.Contains("GET {{url}}/posts HTTP/1.1", result); + Assert.Contains("PATCH {{url}}/posts HTTP/1.1", result); + Assert.Contains("POST {{url}}/posts HTTP/1.1", result); + + // Check content type + Assert.Contains("Content-Type: application/json", result); + + // check the request body + Assert.Contains("\"body\": \"string\"", result); + Assert.Contains("\"id\": 0", result); + Assert.Contains("\"title\": \"string\"", result); + Assert.Contains("\"userId\": 0", result); + } } From 4ccc2633906355ed162f727d591463392316d1a8 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 3 Dec 2024 13:44:20 +0300 Subject: [PATCH 25/66] address pr comments --- .../HTTP/CodeClassDeclarationWriter.cs | 65 ++++++++++++------- .../PathSegmenters/HttpPathSegmenterTests.cs | 6 +- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 0f44852e12..bfc5d78a2e 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -59,7 +59,7 @@ private static List GetAllQueryParameters(CodeClass requestBuilder var queryParameterClasses = requestBuilderClass .GetChildElements(true) .OfType() - .Where(element => element.IsOfKind(CodeClassKind.QueryParameters)) + .Where(static element => element.IsOfKind(CodeClassKind.QueryParameters)) .ToList(); // Collect all query parameter properties into the aggregated list @@ -67,7 +67,7 @@ private static List GetAllQueryParameters(CodeClass requestBuilder { var queryParams = paramCodeClass .Properties - .Where(property => property.IsOfKind(CodePropertyKind.QueryParameter)) + .Where(static property => property.IsOfKind(CodePropertyKind.QueryParameter)) .ToList(); queryParameters.AddRange(queryParams); @@ -90,7 +90,7 @@ private static List GetPathParameters(CodeClass requestBuilderClas .Where(property => property.IsOfKind(CodePropertyKind.PathParameters) && !property.Name.Equals("pathParameters", StringComparison.OrdinalIgnoreCase)) .ToList(); - return pathParameters ?? []; + return pathParameters; } /// @@ -129,7 +129,7 @@ private static void WriteBaseUrl(string? baseUrl, LanguageWriter writer) return requestBuilderClass .GetChildElements(true) .OfType() - .FirstOrDefault(property => property.IsOfKind(CodePropertyKind.UrlTemplate)); + .FirstOrDefault(static property => property.IsOfKind(CodePropertyKind.UrlTemplate)); } /// @@ -200,20 +200,32 @@ private static void WriteHttpParameterProperty(CodeProperty property, LanguageWr /// The list of path parameters. /// The URL template property containing the URL template. /// The base URL. - private static void WriteHttpMethods(CodeClass requestBuilderClass, LanguageWriter writer, List queryParameters, List pathParameters, CodeProperty urlTemplateProperty, string? baseUrl) + private static void WriteHttpMethods( + CodeClass requestBuilderClass, + LanguageWriter writer, + List queryParameters, + List pathParameters, + CodeProperty urlTemplateProperty, + string? baseUrl) { // Retrieve all the HTTP methods of kind RequestExecutor var httpMethods = GetHttpMethods(requestBuilderClass); - for (int i = 0; i < httpMethods.Count; i++) - { - var method = httpMethods[i]; + var methodCount = httpMethods.Count; + var currentIndex = 0; + foreach (var method in httpMethods) + { // Write the method documentation as a comment writer.WriteLine($"# {method.Documentation.DescriptionTemplate}"); // Build the actual URL string and replace all required fields (path and query) with placeholder variables - var url = BuildUrlStringFromTemplate(urlTemplateProperty.DefaultValue, queryParameters, pathParameters, baseUrl); + var url = BuildUrlStringFromTemplate( + urlTemplateProperty.DefaultValue, + queryParameters, + pathParameters, + baseUrl + ); // Write the HTTP operation (e.g., GET, POST, PATCH, etc.) writer.WriteLine($"{method.Name.ToUpperInvariant()} {url} HTTP/1.1"); @@ -221,8 +233,8 @@ private static void WriteHttpMethods(CodeClass requestBuilderClass, LanguageWrit // Write the request body if present WriteRequestBody(method, writer); - // Write an empty line for separation if there are more items that follow - if (i < httpMethods.Count - 1) + // Write a separator if there are more items that follow + if (++currentIndex < methodCount) { writer.WriteLine(); writer.WriteLine("###"); @@ -241,7 +253,7 @@ private static List GetHttpMethods(CodeClass requestBuilderClass) return requestBuilderClass .GetChildElements(true) .OfType() - .Where(element => element.IsOfKind(CodeMethodKind.RequestExecutor)) + .Where(static element => element.IsOfKind(CodeMethodKind.RequestExecutor)) .ToList(); } @@ -253,7 +265,7 @@ private static List GetHttpMethods(CodeClass requestBuilderClass) private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) { // If there is a request body, write it - var requestBody = method.Parameters.FirstOrDefault(param => param.IsOfKind(CodeParameterKind.RequestBody)); + var requestBody = method.Parameters.FirstOrDefault(static param => param.IsOfKind(CodeParameterKind.RequestBody)); if (requestBody is null) return; writer.WriteLine($"Content-Type: {method.RequestBodyContentType}"); @@ -264,11 +276,9 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) // Loop through the properties of the request body and write a JSON object if (requestBody.Type is CodeType ct && ct.TypeDefinition is CodeClass requestBodyClass) { - writer.WriteLine("{"); - writer.IncreaseIndent(); + writer.StartBlock(); WriteProperties(requestBodyClass, writer); - writer.DecreaseIndent(); - writer.WriteLine("}"); + writer.CloseBlock(); } } @@ -279,20 +289,24 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) /// The language writer to write the properties to. private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter writer) { - var properties = requestBodyClass.Properties.Where(prop => prop.IsOfKind(CodePropertyKind.Custom)).ToList(); - for (int i = 0; i < properties.Count; i++) + var properties = requestBodyClass.Properties + .Where(static prop => prop.IsOfKind(CodePropertyKind.Custom)) + .ToArray(); + + var propertyCount = properties.Length; + var currentIndex = 0; + + foreach (var prop in properties) { - var prop = properties[i]; var propName = $"\"{prop.Name}\""; writer.Write($"{propName}: "); + if (prop.Type is CodeType propType && propType.TypeDefinition is CodeClass propClass) { // If the property is an object, write a JSON representation recursively - writer.WriteLine("{", includeIndent: false); - writer.IncreaseIndent(); + writer.StartBlock("{", increaseIndent: false); WriteProperties(propClass, writer); - writer.DecreaseIndent(); - writer.Write("}"); + writer.CloseBlock(); } else { @@ -300,7 +314,7 @@ private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter w } // Add a trailing comma if there are more properties to be written - if (i < properties.Count - 1) + if (++currentIndex < propertyCount) { writer.WriteLine(",", includeIndent: false); } @@ -317,6 +331,7 @@ private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter w } } + /// /// Gets the default value for the given property. /// diff --git a/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs b/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs index b04f9469ba..915ec3c01a 100644 --- a/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs +++ b/tests/Kiota.Builder.Tests/PathSegmenters/HttpPathSegmenterTests.cs @@ -1,4 +1,5 @@ -using Kiota.Builder.CodeDOM; +using System.IO; +using Kiota.Builder.CodeDOM; using Kiota.Builder.PathSegmenters; using Xunit; @@ -10,7 +11,8 @@ public class HttpPathSegmenterTests public HttpPathSegmenterTests() { - segmenter = new HttpPathSegmenter("D:\\source\\repos\\kiota-sample", "client"); + var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + segmenter = new HttpPathSegmenter(tempFilePath, "client"); } [Fact] From d9da63683234df05c014511b400f02954f97b3f7 Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 3 Dec 2024 17:24:01 +0300 Subject: [PATCH 26/66] address pr comments --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 16 ++++++++-------- .../Writers/HTTP/CodeClassDeclarationWriter.cs | 14 +++----------- .../Writers/HTTP/HttpConventionService.cs | 1 - 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 3b23a81703..6c5da85000 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -47,7 +47,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken .FindChildrenByName(_configuration.ClientClassName)? .FirstOrDefault()? .Methods? - .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.ClientConstructor))? + .FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor))? .BaseUrl; } @@ -76,7 +76,7 @@ private static void SetBaseUrlForRequestBuilderMethods(CodeElement current, stri CrawlTree(current, (element) => SetBaseUrlForRequestBuilderMethods(element, baseUrl)); } - private static void RemoveUnusedCodeElements(CodeElement element) + private void RemoveUnusedCodeElements(CodeElement element) { if (!IsRequestBuilderClass(element) || IsBaseRequestBuilder(element) || IsRequestBuilderClassWithoutAnyHttpOperations(element)) { @@ -104,13 +104,13 @@ private static void AddPathParameters(CodeElement element) .FirstOrDefault()? .GetChildElements(false) .OfType() - .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)); + .FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)); if (codeIndexer is not null) { // Retrieve all the parameters of kind CodeParameterKind.Custom var customParameters = codeIndexer.Parameters - .Where(param => param.IsOfKind(CodeParameterKind.Custom)) + .Where(static param => param.IsOfKind(CodeParameterKind.Custom)) .ToList(); // For each parameter: @@ -140,15 +140,15 @@ private static bool IsRequestBuilderClass(CodeElement element) return element is CodeClass code && code.IsOfKind(CodeClassKind.RequestBuilder); } - private static bool IsBaseRequestBuilder(CodeElement element) + private bool IsBaseRequestBuilder(CodeElement element) { - return element is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.RequestBuilder) && - codeClass.Properties.Any(property => property.IsOfKind(CodePropertyKind.UrlTemplate) && string.Equals(property.DefaultValue, "\"{+baseurl}\"", StringComparison.Ordinal)); + return element is CodeClass codeClass && + codeClass.Name.Equals(_configuration.ClientClassName, StringComparison.Ordinal); } private static bool IsRequestBuilderClassWithoutAnyHttpOperations(CodeElement element) { return element is CodeClass codeClass && codeClass.IsOfKind(CodeClassKind.RequestBuilder) && - !codeClass.Methods.Any(method => method.IsOfKind(CodeMethodKind.RequestExecutor)); + !codeClass.Methods.Any(static method => method.IsOfKind(CodeMethodKind.RequestExecutor)); } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index bfc5d78a2e..a1a9142b5b 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -361,19 +361,11 @@ private static string BuildUrlStringFromTemplate(string urlTemplateString, List< // Build RequestInformation using the URL var requestInformation = new RequestInformation() { - UrlTemplate = urlTemplateString + UrlTemplate = urlTemplateString, + QueryParameters = queryParameters.ToDictionary(item => item.WireName, item => $"{{{{{item.Name.ToFirstCharacterLowerCase()}}}}}" as object), + PathParameters = pathParameters.ToDictionary(item => item.WireName, item => $"{{{{{item.Name.ToFirstCharacterLowerCase()}}}}}" as object), }; - queryParameters?.ForEach(param => - { - requestInformation.QueryParameters.Add(param.WireName, $"{{{{{param.Name.ToFirstCharacterLowerCase()}}}}}"); - }); - - pathParameters?.ForEach(param => - { - requestInformation.PathParameters.Add(param.WireName, $"{{{{{param.Name.ToFirstCharacterLowerCase()}}}}}"); - }); - // Erase baseUrl and use the placeholder variable {baseUrl} already defined in the snippet return requestInformation.URI.ToString().Replace(baseUrl, $"{{{{{BaseUrlPropertyName}}}}}", StringComparison.InvariantCultureIgnoreCase); } diff --git a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs index 6e41f8c8c7..f51ca9f5d6 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs @@ -12,7 +12,6 @@ public HttpConventionService() public override string StreamTypeName => "stream"; public override string VoidTypeName => "void"; public override string DocCommentPrefix => "###"; - public static readonly char NullableMarker = '?'; public static string NullableMarkerAsString => "?"; public override string ParseNodeInterfaceName => "ParseNode"; public override bool WriteShortDescription(IDocumentedElement element, LanguageWriter writer, string prefix = "", string suffix = "") From ef519d176a2425fa05c7dd00d8a55b10753386ad Mon Sep 17 00:00:00 2001 From: koros Date: Tue, 3 Dec 2024 17:44:38 +0300 Subject: [PATCH 27/66] address pr comments --- .../HTTP/CodeClassDeclarationWriter.cs | 21 +---- .../Writers/HTTP/HttpConventionService.cs | 20 ++++- .../HTTP/HttpConventionServiceTests.cs | 78 +++++++++++++++++++ 3 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 tests/Kiota.Builder.Tests/Writers/HTTP/HttpConventionServiceTests.cs diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index a1a9142b5b..e4ad74acae 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -310,7 +310,7 @@ private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter w } else { - writer.Write(GetDefaultValueForProperty(prop), includeIndent: false); + writer.Write(HttpConventionService.GetDefaultValueForProperty(prop), includeIndent: false); } // Add a trailing comma if there are more properties to be written @@ -331,25 +331,6 @@ private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter w } } - - /// - /// Gets the default value for the given property. - /// - /// The property to get the default value for. - /// The default value as a string. - private static string GetDefaultValueForProperty(CodeProperty codeProperty) - { - return codeProperty.Type.Name switch - { - "int" or "integer" => "0", - "string" => "\"string\"", - "bool" or "boolean" => "false", - _ when codeProperty.Type is CodeType enumType && enumType.TypeDefinition is CodeEnum enumDefinition => - enumDefinition.Options.FirstOrDefault()?.Name is string enumName ? $"\"{enumName}\"" : "null", - _ => "null" - }; - } - private static string BuildUrlStringFromTemplate(string urlTemplateString, List queryParameters, List pathParameters, string? baseUrl) { // Use the provided baseUrl or default to "http://localhost/" diff --git a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs index f51ca9f5d6..72e1c43fbe 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpConventionService.cs @@ -1,5 +1,5 @@ using System; - +using System.Linq; using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; @@ -83,4 +83,22 @@ public override string GetParameterSignature(CodeParameter parameter, CodeElemen }; return $"{parameter.Name.ToFirstCharacterLowerCase()} : {parameterType}{defaultValue}"; } + + /// + /// Gets the default value for the given property. + /// + /// The property to get the default value for. + /// The default value as a string. + public static string GetDefaultValueForProperty(CodeProperty codeProperty) + { + return codeProperty?.Type.Name switch + { + "int" or "integer" => "0", + "string" => "\"string\"", + "bool" or "boolean" => "false", + _ when codeProperty?.Type is CodeType enumType && enumType.TypeDefinition is CodeEnum enumDefinition => + enumDefinition.Options.FirstOrDefault()?.Name is string enumName ? $"\"{enumName}\"" : "null", + _ => "null" + }; + } } diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/HttpConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/HttpConventionServiceTests.cs new file mode 100644 index 0000000000..f91371712f --- /dev/null +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/HttpConventionServiceTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using Kiota.Builder.CodeDOM; +using Kiota.Builder.Writers; +using Kiota.Builder.Writers.Http; +using Xunit; + +namespace Kiota.Builder.Tests.Writers.Http; +public sealed class HttpConventionServiceTest +{ + + [Fact] + public void TestGetDefaultValueForProperty_Int() + { + // Arrange + var codeProperty = new CodeProperty + { + Type = new CodeType { Name = "int" } + }; + + // Act + var result = HttpConventionService.GetDefaultValueForProperty(codeProperty); + + // Assert + Assert.Equal("0", result); + } + + [Fact] + public void TestGetDefaultValueForProperty_String() + { + // Arrange + var codeProperty = new CodeProperty + { + Type = new CodeType { Name = "string" } + }; + + // Act + var result = HttpConventionService.GetDefaultValueForProperty(codeProperty); + + // Assert + Assert.Equal("\"string\"", result); + } + + [Fact] + public void TestGetDefaultValueForProperty_Bool() + { + // Arrange + var codeProperty = new CodeProperty + { + Type = new CodeType { Name = "bool" } + }; + + // Act + var result = HttpConventionService.GetDefaultValueForProperty(codeProperty); + + // Assert + Assert.Equal("false", result); + } + + [Fact] + public void TestGetDefaultValueForProperty_Null() + { + // Arrange + var codeProperty = new CodeProperty + { + Type = new CodeType { Name = "unknown" } + }; + + // Act + var result = HttpConventionService.GetDefaultValueForProperty(codeProperty); + + // Assert + Assert.Equal("null", result); + } +} From 51c6e2540b150c8252c29cbe139873d2db29dcaf Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 4 Dec 2024 10:34:16 +0300 Subject: [PATCH 28/66] format code --- .../Writers/HTTP/HttpConventionServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/HttpConventionServiceTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/HttpConventionServiceTests.cs index f91371712f..23efdcfd4f 100644 --- a/tests/Kiota.Builder.Tests/Writers/HTTP/HttpConventionServiceTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/HttpConventionServiceTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; From fdfabb77c29709c31f60204418decae309c85504 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 4 Dec 2024 14:54:34 +0300 Subject: [PATCH 29/66] restore formating for nested json objects --- .../HTTP/CodeClassDeclarationWriter.cs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index e4ad74acae..2be8d10a8f 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -298,29 +298,22 @@ private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter w foreach (var prop in properties) { + // Add a trailing comma if there are more properties to be written + var separator = currentIndex < propertyCount - 1 ? "," : string.Empty; var propName = $"\"{prop.Name}\""; writer.Write($"{propName}: "); if (prop.Type is CodeType propType && propType.TypeDefinition is CodeClass propClass) { // If the property is an object, write a JSON representation recursively - writer.StartBlock("{", increaseIndent: false); + writer.WriteLine("{", includeIndent: false); + writer.IncreaseIndent(); WriteProperties(propClass, writer); - writer.CloseBlock(); + writer.CloseBlock($"}}{separator}"); } else { - writer.Write(HttpConventionService.GetDefaultValueForProperty(prop), includeIndent: false); - } - - // Add a trailing comma if there are more properties to be written - if (++currentIndex < propertyCount) - { - writer.WriteLine(",", includeIndent: false); - } - else - { - writer.WriteLine(); + writer.WriteLine($"{HttpConventionService.GetDefaultValueForProperty(prop)}{separator}", includeIndent: false); } } From 407f61a18298b34d9644a61837f26d32224daf18 Mon Sep 17 00:00:00 2001 From: koros Date: Thu, 5 Dec 2024 12:09:39 +0300 Subject: [PATCH 30/66] address pr comments --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 6c5da85000..d5c048f54d 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -44,8 +44,7 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken { return element.GetImmediateParentOfType() .GetRootNamespace()? - .FindChildrenByName(_configuration.ClientClassName)? - .FirstOrDefault()? + .FindChildByName(_configuration.ClientClassName)? .Methods? .FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.ClientConstructor))? .BaseUrl; From 7b9b16d3fc747aec8334a724f087d86ddacf747c Mon Sep 17 00:00:00 2001 From: EvansA Date: Mon, 13 Jan 2025 18:06:09 +0300 Subject: [PATCH 31/66] Remove no-op writers --- src/Kiota.Builder/Writers/HTTP/HttpWriter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs index 14efff58bd..e0aafbbc8a 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs @@ -9,10 +9,7 @@ public HttpWriter(string rootPath, string clientNamespaceName) PathSegmenter = new HttpPathSegmenter(rootPath, clientNamespaceName); var conventionService = new HttpConventionService(); AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); - AddOrReplaceCodeElementWriter(new CodeBlockEndWriter()); AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); - AddOrReplaceCodeElementWriter(new CodeNamespaceWriter(conventionService)); - AddOrReplaceCodeElementWriter(new CodeEnumWriter(conventionService)); AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); } } From c505b92e53d8907268eee4868022c52a0f7d6b8e Mon Sep 17 00:00:00 2001 From: EvansA Date: Mon, 13 Jan 2025 21:48:06 +0300 Subject: [PATCH 32/66] Move path parameter addition to refiner --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 38 ++++++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index d5c048f54d..fc8553d83b 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -37,9 +37,42 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken SetBaseUrlForRequestBuilderMethods(generatedCode, GetBaseUrl(generatedCode)); // Remove unused code from the DOM e.g Models, BarrelInitializers, e.t.c RemoveUnusedCodeElements(generatedCode); + AddPathParametersToIndexerMethods(generatedCode); }, cancellationToken); } + private void AddPathParametersToIndexerMethods(CodeElement element) + { + if (element is CodeMethod codeMethod && codeMethod.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)) + { + var parentClass = element.GetImmediateParentOfType(); + if (parentClass is not null) + { + // Retrieve all the parameters of kind CodeParameterKind.Custom + var customParameters = codeMethod.Parameters + .Where(param => param.IsOfKind(CodeParameterKind.Custom)) + .ToList(); + + customParameters.ForEach(param => + { + // Create a new property of kind CodePropertyKind.PathParameters using the parameter and add it to the parentClass + var pathParameterProperty = new CodeProperty + { + Name = param.Name, + Kind = CodePropertyKind.PathParameters, + Type = param.Type, + Access = AccessModifier.Public, + DefaultValue = param.DefaultValue, + SerializationName = param.SerializationName, + Documentation = param.Documentation + }; + parentClass.AddProperty(pathParameterProperty); + }); + } + } + CrawlTree(element, AddPathParametersToIndexerMethods); + } + private string? GetBaseUrl(CodeElement element) { return element.GetImmediateParentOfType() @@ -82,11 +115,6 @@ private void RemoveUnusedCodeElements(CodeElement element) var parentNameSpace = element.GetImmediateParentOfType(); parentNameSpace?.RemoveChildElement(element); } - else - { - // Add path variables - AddPathParameters(element); - } CrawlTree(element, RemoveUnusedCodeElements); } From 6ff322a0eb13cefede5a4a391a15a0290a476b57 Mon Sep 17 00:00:00 2001 From: EvansA Date: Tue, 14 Jan 2025 09:25:35 +0300 Subject: [PATCH 33/66] Add launch config for Http --- .vscode/launch.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index f4bcd561b3..59dcae31c9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -383,6 +383,22 @@ "type": "coreclr", "request": "attach", "processId": "${command:pickProcess}" + }, + { + "name": "Launch Http", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll", + "args": [ + "generate", + "--openapi", "C:\\Users\\evansaboge\\Documents\\Tests\\RestClientTests\\openapi.yaml", + "--language", "http", + "--output", "C:\\Users\\evansaboge\\Documents\\Tests\\RestClientTests\\HTTP1" + ], + "cwd": "${workspaceFolder}/src/kiota", + "stopAtEntry": false, + "console": "internalConsole" } ] } \ No newline at end of file From 78b2c1d8370c4c5f168bf6f7985ef71566a61ba3 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Tue, 14 Jan 2025 11:32:18 +0300 Subject: [PATCH 34/66] Update launch config --- .vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 0a0e600987..6371a0ba19 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -413,9 +413,9 @@ "program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll", "args": [ "generate", - "--openapi", "C:\\Users\\evansaboge\\Documents\\Tests\\RestClientTests\\openapi.yaml", + "--openapi", "C:\\Users\\evansaboge\\Documents\\RestClient\\openapi.yaml", "--language", "http", - "--output", "C:\\Users\\evansaboge\\Documents\\Tests\\RestClientTests\\HTTP1" + "--output", "C:\\Users\\evansaboge\\Documents\\RestClient\\HTTP1" ], "cwd": "${workspaceFolder}/src/kiota", "stopAtEntry": false, From 2daae8332c8390e7de8ef52393c2f788ad3c5b16 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Tue, 14 Jan 2025 12:33:54 +0300 Subject: [PATCH 35/66] Update refiner --- .vscode/launch.json | 2 +- src/Kiota.Builder/Refiners/HttpRefiner.cs | 45 ++++------------------- 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 6371a0ba19..e35088074b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -422,4 +422,4 @@ "console": "internalConsole" } ] -} +} \ No newline at end of file diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index fc8553d83b..4c22b02778 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -35,44 +36,12 @@ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken ); cancellationToken.ThrowIfCancellationRequested(); SetBaseUrlForRequestBuilderMethods(generatedCode, GetBaseUrl(generatedCode)); + AddPathParameters(generatedCode); // Remove unused code from the DOM e.g Models, BarrelInitializers, e.t.c RemoveUnusedCodeElements(generatedCode); - AddPathParametersToIndexerMethods(generatedCode); }, cancellationToken); } - private void AddPathParametersToIndexerMethods(CodeElement element) - { - if (element is CodeMethod codeMethod && codeMethod.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)) - { - var parentClass = element.GetImmediateParentOfType(); - if (parentClass is not null) - { - // Retrieve all the parameters of kind CodeParameterKind.Custom - var customParameters = codeMethod.Parameters - .Where(param => param.IsOfKind(CodeParameterKind.Custom)) - .ToList(); - - customParameters.ForEach(param => - { - // Create a new property of kind CodePropertyKind.PathParameters using the parameter and add it to the parentClass - var pathParameterProperty = new CodeProperty - { - Name = param.Name, - Kind = CodePropertyKind.PathParameters, - Type = param.Type, - Access = AccessModifier.Public, - DefaultValue = param.DefaultValue, - SerializationName = param.SerializationName, - Documentation = param.Documentation - }; - parentClass.AddProperty(pathParameterProperty); - }); - } - } - CrawlTree(element, AddPathParametersToIndexerMethods); - } - private string? GetBaseUrl(CodeElement element) { return element.GetImmediateParentOfType() @@ -118,11 +87,8 @@ private void RemoveUnusedCodeElements(CodeElement element) CrawlTree(element, RemoveUnusedCodeElements); } - private static void AddPathParameters(CodeElement element) + private void AddPathParameters(CodeElement element) { - // Target RequestBuilder Classes only - if (element is not CodeClass codeClass) return; - var parent = element.GetImmediateParentOfType().Parent; while (parent is not null) { @@ -154,12 +120,15 @@ private static void AddPathParameters(CodeElement element) SerializationName = param.SerializationName, Documentation = param.Documentation }; - codeClass.AddProperty(pathParameterProperty); + + if (element is CodeClass codeClass) + codeClass.AddProperty(pathParameterProperty); } } parent = parent.Parent?.GetImmediateParentOfType(); } + CrawlTree(element, AddPathParameters); } private static bool IsRequestBuilderClass(CodeElement element) From a99c4a847d875610c1dce96bd03129e3bc6b5533 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Tue, 14 Jan 2025 18:59:43 +0300 Subject: [PATCH 36/66] Fix circular reference --- .vscode/launch.json | 13 +++++---- .../HTTP/CodeClassDeclarationWriter.cs | 28 +++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e35088074b..b780e8e5c7 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -412,14 +412,17 @@ "preLaunchTask": "build", "program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll", "args": [ - "generate", - "--openapi", "C:\\Users\\evansaboge\\Documents\\RestClient\\openapi.yaml", - "--language", "http", - "--output", "C:\\Users\\evansaboge\\Documents\\RestClient\\HTTP1" + "generate", + "--openapi", + "https://raw.githubusercontent.com/microsoftgraph/msgraph-sdk-powershell/dev/openApiDocs/v1.0/Mail.yml", + "--language", + "http", + "--output", + "${workspaceFolder}/samples/msgraph-mail/http", ], "cwd": "${workspaceFolder}/src/kiota", "stopAtEntry": false, "console": "internalConsole" } ] -} \ No newline at end of file +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 2be8d10a8f..279a02e295 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -287,19 +287,34 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) /// /// The request body class containing the properties. /// The language writer to write the properties to. - private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter writer) + private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter writer, HashSet? processedClasses = null, int depth = 0) { + + if (processedClasses == null) + { + processedClasses = new HashSet(); + } + + // Add the current class to the set of processed classes + if (!processedClasses.Add(requestBodyClass)) + { + // If the class is already processed, write its properties again up to a certain depth + if (depth >= 3) + { + return; + } + } + var properties = requestBodyClass.Properties .Where(static prop => prop.IsOfKind(CodePropertyKind.Custom)) .ToArray(); var propertyCount = properties.Length; - var currentIndex = 0; foreach (var prop in properties) { // Add a trailing comma if there are more properties to be written - var separator = currentIndex < propertyCount - 1 ? "," : string.Empty; + var separator = ","; var propName = $"\"{prop.Name}\""; writer.Write($"{propName}: "); @@ -308,7 +323,7 @@ private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter w // If the property is an object, write a JSON representation recursively writer.WriteLine("{", includeIndent: false); writer.IncreaseIndent(); - WriteProperties(propClass, writer); + WriteProperties(propClass, writer, processedClasses, depth + 1); writer.CloseBlock($"}}{separator}"); } else @@ -317,10 +332,13 @@ private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter w } } + // Remove the current class from the set of processed classes after processing + processedClasses.Remove(requestBodyClass); + // If the class extends another class, write properties of the base class if (requestBodyClass.StartBlock.Inherits?.TypeDefinition is CodeClass baseClass) { - WriteProperties(baseClass, writer); + WriteProperties(baseClass, writer, processedClasses, depth + 1); } } From a96a7e8139019435c52fb8fd3760054a40e81d93 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Wed, 15 Jan 2025 15:11:44 +0300 Subject: [PATCH 37/66] Settings file scafolding --- src/Kiota.Builder/KiotaBuilder.cs | 7 ++ .../Settings/ISettingsManagementService.cs | 42 +++++++++++ src/Kiota.Builder/Settings/SettingsFile.cs | 71 +++++++++++++++++++ .../Settings/SettingsFileGenerationContext.cs | 12 ++++ .../Settings/SettingsFileManagementService.cs | 60 ++++++++++++++++ .../HTTP/CodeClassDeclarationWriter.cs | 6 +- 6 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 src/Kiota.Builder/Settings/ISettingsManagementService.cs create mode 100644 src/Kiota.Builder/Settings/SettingsFile.cs create mode 100644 src/Kiota.Builder/Settings/SettingsFileGenerationContext.cs create mode 100644 src/Kiota.Builder/Settings/SettingsFileManagementService.cs diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 450e392103..d9e39a4bef 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -26,6 +26,7 @@ using Kiota.Builder.OpenApiExtensions; using Kiota.Builder.Plugins; using Kiota.Builder.Refiners; +using Kiota.Builder.Settings; using Kiota.Builder.WorkspaceManagement; using Kiota.Builder.Writers; using Microsoft.Extensions.Logging; @@ -46,6 +47,7 @@ public partial class KiotaBuilder private readonly ParallelOptions parallelOptions; private readonly HttpClient httpClient; private OpenApiDocument? openApiDocument; + private readonly SettingsFileManagementService settingsFileManagementService = new(); internal void SetOpenApiDocument(OpenApiDocument document) => openApiDocument = document ?? throw new ArgumentNullException(nameof(document)); public KiotaBuilder(ILogger logger, GenerationConfiguration config, HttpClient client, bool useKiotaConfig = false) @@ -285,6 +287,11 @@ public async Task GenerateClientAsync(CancellationToken cancellationToken) sw.Start(); await CreateLanguageSourceFilesAsync(config.Language, generatedCode, cancellationToken).ConfigureAwait(false); StopLogAndReset(sw, $"step {++stepId} - writing files - took"); + + if (config.Language == GenerationLanguage.HTTP && openApiDocument is not null) + { + await settingsFileManagementService.WriteSettingsFileAsync(config.OutputPath, openApiDocument, cancellationToken).ConfigureAwait(false); + } return stepId; }, cancellationToken).ConfigureAwait(false); } diff --git a/src/Kiota.Builder/Settings/ISettingsManagementService.cs b/src/Kiota.Builder/Settings/ISettingsManagementService.cs new file mode 100644 index 0000000000..339e55ee4c --- /dev/null +++ b/src/Kiota.Builder/Settings/ISettingsManagementService.cs @@ -0,0 +1,42 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; + +namespace Kiota.Builder.Settings; +/// +/// A service that manages the settings file for http language snippets. +/// +public interface ISettingsManagementService +{ + /// + /// Gets the settings file for a Kiota project by crawling the directory tree. + /// + /// + /// + string GetDirectoryContainingSettingsFile(string searchDirectory); + + /// + /// Gets the settings from a directory. + /// + /// + /// + Task GetSettingsFromDirectoryAsync(string directoryPath, CancellationToken cancellationToken); + + /// + /// Gets the settings from a stream. + /// + /// + /// + Task GetSettingsFromStreamAsync(Stream stream); + + /// + /// Writes the settings file to a directory. + /// + /// + /// OpenApi document + /// + /// + Task WriteSettingsFileAsync(string directoryPath, OpenApiDocument openApiDocument, CancellationToken cancellationToken); +} diff --git a/src/Kiota.Builder/Settings/SettingsFile.cs b/src/Kiota.Builder/Settings/SettingsFile.cs new file mode 100644 index 0000000000..f5751a605d --- /dev/null +++ b/src/Kiota.Builder/Settings/SettingsFile.cs @@ -0,0 +1,71 @@ +using System.Text.Json.Serialization; +using Kiota.Builder.Configuration; + +namespace Kiota.Builder.Settings; +public class SettingsFile +{ + [JsonPropertyName("rest-client.environmentVariables")] + public EnvironmentVariables EnvironmentVariables + { + get; set; + } + + public SettingsFile() + { + EnvironmentVariables = new EnvironmentVariables(); + } +} + +public class EnvironmentVariables +{ + [JsonPropertyName("$shared")] + public SharedAuth Shared + { + get; set; + } + + [JsonPropertyName("remote")] + public AuthenticationSettings Remote + { + get; set; + } + + [JsonPropertyName("development")] + public AuthenticationSettings Development + { + get; set; + } + + public EnvironmentVariables() + { + Shared = new SharedAuth(); + Remote = new AuthenticationSettings(); + Development = new AuthenticationSettings(); + } +} + +public class SharedAuth +{ + +} + +public class AuthenticationSettings +{ + public string HostAddress + { + get; set; + } + public string? BasicAuth + { + get; set; + } + public string? Bearer + { + get; set; + } + + public AuthenticationSettings() + { + HostAddress = ""; + } +} diff --git a/src/Kiota.Builder/Settings/SettingsFileGenerationContext.cs b/src/Kiota.Builder/Settings/SettingsFileGenerationContext.cs new file mode 100644 index 0000000000..d5dcb10899 --- /dev/null +++ b/src/Kiota.Builder/Settings/SettingsFileGenerationContext.cs @@ -0,0 +1,12 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; +using Kiota.Builder.Lock; + +namespace Kiota.Builder.Settings; + +[JsonSerializable(typeof(SettingsFile))] +internal partial class SettingsFileGenerationContext : JsonSerializerContext +{ +} diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs new file mode 100644 index 0000000000..15bca62b5e --- /dev/null +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -0,0 +1,60 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; + +namespace Kiota.Builder.Settings; + +public class SettingsFileManagementService : ISettingsManagementService +{ + internal const string SettingsFileName = "settings.json"; + public string GetDirectoryContainingSettingsFile(string searchDirectory) + { + throw new NotImplementedException(); + } + + public Task GetSettingsFromDirectoryAsync(string directoryPath, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task GetSettingsFromStreamAsync(Stream stream) + { + throw new NotImplementedException(); + } + + public Task WriteSettingsFileAsync(string directoryPath, OpenApiDocument openApiDocument, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrEmpty(directoryPath); + ArgumentNullException.ThrowIfNull(openApiDocument); + var settings = GenerateSettingsFile(openApiDocument); + return WriteSettingsFileInternalAsync(directoryPath, settings, cancellationToken); + } + + private static SettingsFile GenerateSettingsFile(OpenApiDocument openApiDocument) + { + var settings = new SettingsFile(); + settings.EnvironmentVariables.Development.HostAddress = openApiDocument.Servers[0].Url; + settings.EnvironmentVariables.Remote.HostAddress = openApiDocument.Servers[0].Url; + return settings; + } + + private static readonly JsonSerializerOptions options = new() + { + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + + private static readonly SettingsFileGenerationContext context = new(options); + + private static async Task WriteSettingsFileInternalAsync(string directoryPath, SettingsFile settings, CancellationToken cancellationToken) + { + var filePath = Path.Combine(directoryPath, SettingsFileName); +#pragma warning disable CA2007 // Dispose objects before losing scope + await using var fileStream = File.Open(filePath, FileMode.Create); +#pragma warning disable CA2007 + await JsonSerializer.SerializeAsync(fileStream, settings, context.SettingsFile, cancellationToken).ConfigureAwait(false); + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 279a02e295..4a033171b2 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -8,7 +8,7 @@ namespace Kiota.Builder.Writers.Http; public class CodeClassDeclarationWriter(HttpConventionService conventionService) : CodeProprietableBlockDeclarationWriter(conventionService) { - private const string BaseUrlPropertyName = "url"; + private const string BaseUrlPropertyName = "hostAddress"; protected override void WriteTypeDeclaration(ClassDeclaration codeElement, LanguageWriter writer) { @@ -30,7 +30,7 @@ protected override void WriteTypeDeclaration(ClassDeclaration codeElement, Langu var baseUrl = GetBaseUrl(requestBuilderClass); // Write the baseUrl variable - WriteBaseUrl(baseUrl, writer); + //WriteBaseUrl(baseUrl, writer); // Extract and write the URL template WriteUrlTemplate(urlTemplateProperty, writer); @@ -102,7 +102,7 @@ private static void WriteBaseUrl(string? baseUrl, LanguageWriter writer) { // Write the base URL variable to the writer writer.WriteLine($"# Base url for the server/host"); - writer.WriteLine($"@{BaseUrlPropertyName} = {baseUrl}"); + writer.WriteLine($"@{BaseUrlPropertyName} = {{hostAddress}}"); writer.WriteLine(); } From 958c5c34795951055a532c034540ac76dbedbb5f Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Thu, 16 Jan 2025 11:09:11 +0300 Subject: [PATCH 38/66] Add security schemes to code DOM --- src/Kiota.Builder/KiotaBuilder.cs | 79 +++++++++++++++++++ src/Kiota.Builder/Settings/SettingsFile.cs | 11 ++- .../Settings/SettingsFileManagementService.cs | 10 ++- .../Writers/HTTP/Authentication.cs | 10 +++ .../HTTP/CodeClassDeclarationWriter.cs | 8 ++ 5 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 src/Kiota.Builder/Writers/HTTP/Authentication.cs diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index d9e39a4bef..893523ba0f 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -561,6 +561,81 @@ public CodeNamespace CreateSourceModel(OpenApiUrlTreeNode? root) return rootNamespace; } + private void ProcessSecurityRequirements(OpenApiOperation operation, CodeClass codeClass) + { + if (openApiDocument is null) + { + logger.LogWarning("OpenAPI document is null"); + return; + } + + if (operation.Security == null || !operation.Security.Any()) + return; + + var securitySchemes = openApiDocument.Components.SecuritySchemes; + foreach (var securityRequirement in operation.Security) + { + foreach (var scheme in securityRequirement.Keys) + { + var securityScheme = securitySchemes[scheme.Reference.Id]; + switch (securityScheme.Type) + { + case SecuritySchemeType.Http: + AddHttpSecurity(codeClass, securityScheme); + break; + case SecuritySchemeType.ApiKey: + AddApiKeySecurity(codeClass, securityScheme); + break; + case SecuritySchemeType.OAuth2: + AddOAuth2Security(codeClass, securityScheme); + break; + default: + logger.LogWarning("Unsupported security scheme type: {Type}", securityScheme.Type); + break; + } + } + } + } + + private void AddHttpSecurity(CodeClass codeClass, OpenApiSecurityScheme securityScheme) + { + codeClass.AddProperty( + new CodeProperty + { + Name = "", + Type = new CodeType { Name = Authentication.Basic.ToString(), IsExternal = true }, + Kind = CodePropertyKind.Headers, + DefaultValue = $"{securityScheme.Scheme}Auth" + } + ); + } + + private void AddApiKeySecurity(CodeClass codeClass, OpenApiSecurityScheme securityScheme) + { + codeClass.AddProperty( + new CodeProperty + { + Name = "", + Type = new CodeType { Name = Authentication.APIKey.ToString(), IsExternal = true }, + Kind = CodePropertyKind.Headers, + DefaultValue = $"{securityScheme.Scheme}Auth" + } + ); + } + + private void AddOAuth2Security(CodeClass codeClass, OpenApiSecurityScheme securityScheme) + { + codeClass.AddProperty( + new CodeProperty + { + Name = "bearer", + Type = new CodeType { Name = Authentication.OAuthV2.ToString(), IsExternal = true }, + Kind = CodePropertyKind.Headers, + DefaultValue = $"{securityScheme.Scheme}Auth" + } + ); + } + /// /// Manipulate CodeDOM for language specific issues /// @@ -678,7 +753,11 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr foreach (var operation in currentNode .PathItems[Constants.DefaultOpenApiLabel] .Operations) + { + CreateOperationMethods(currentNode, operation.Key, operation.Value, codeClass); + ProcessSecurityRequirements(operation.Value, codeClass); + } } if (rootNamespace != null) diff --git a/src/Kiota.Builder/Settings/SettingsFile.cs b/src/Kiota.Builder/Settings/SettingsFile.cs index f5751a605d..30475af77b 100644 --- a/src/Kiota.Builder/Settings/SettingsFile.cs +++ b/src/Kiota.Builder/Settings/SettingsFile.cs @@ -24,13 +24,11 @@ public SharedAuth Shared get; set; } - [JsonPropertyName("remote")] public AuthenticationSettings Remote { get; set; } - [JsonPropertyName("development")] public AuthenticationSettings Development { get; set; @@ -55,11 +53,18 @@ public string HostAddress { get; set; } + public string? BasicAuth { get; set; } - public string? Bearer + + public string? BearerAuth + { + get; set; + } + + public string? ApiKey { get; set; } diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index 15bca62b5e..33e31f8de5 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -51,7 +51,15 @@ private static SettingsFile GenerateSettingsFile(OpenApiDocument openApiDocument private static async Task WriteSettingsFileInternalAsync(string directoryPath, SettingsFile settings, CancellationToken cancellationToken) { - var filePath = Path.Combine(directoryPath, SettingsFileName); + var parentDirectoryPath = Path.GetDirectoryName(directoryPath); + var vscodeDirectoryPath = Path.Combine(parentDirectoryPath!, ".vscode"); + if (!Directory.Exists(vscodeDirectoryPath)) + { + Directory.CreateDirectory(vscodeDirectoryPath); + } + + var filePath = Path.Combine(vscodeDirectoryPath, SettingsFileName); + // var filePath = Path.Combine(directoryPath, SettingsFileName); #pragma warning disable CA2007 // Dispose objects before losing scope await using var fileStream = File.Open(filePath, FileMode.Create); #pragma warning disable CA2007 diff --git a/src/Kiota.Builder/Writers/HTTP/Authentication.cs b/src/Kiota.Builder/Writers/HTTP/Authentication.cs new file mode 100644 index 0000000000..dd0287aad5 --- /dev/null +++ b/src/Kiota.Builder/Writers/HTTP/Authentication.cs @@ -0,0 +1,10 @@ +namespace Kiota.Builder.Writers; +public enum Authentication +{ + None, + Basic, + Bearer, + OAuthV2, + APIKey, + Custom +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 4a033171b2..1e8603886b 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -230,6 +230,14 @@ private static void WriteHttpMethods( // Write the HTTP operation (e.g., GET, POST, PATCH, etc.) writer.WriteLine($"{method.Name.ToUpperInvariant()} {url} HTTP/1.1"); + var authenticationMethod = requestBuilderClass + .Properties + .FirstOrDefault(static prop => prop.IsOfKind(CodePropertyKind.Headers)); + + if (authenticationMethod != null && authenticationMethod.Name.Equals("authentication-scheme", StringComparison.OrdinalIgnoreCase)) { + writer.WriteLine($"Authorization: {{{{{authenticationMethod.DefaultValue}}}}}"); + } + // Write the request body if present WriteRequestBody(method, writer); From 2d5489761053dfc27259fec7c64318482a48edc3 Mon Sep 17 00:00:00 2001 From: Onokaev Date: Thu, 16 Jan 2025 11:34:14 +0300 Subject: [PATCH 39/66] Fix enum comparison --- src/Kiota.Builder/KiotaBuilder.cs | 3 --- src/Kiota.Builder/Writers/HTTP/Authentication.cs | 2 -- src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs | 4 ++-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 893523ba0f..b936cb4333 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -602,7 +602,6 @@ private void AddHttpSecurity(CodeClass codeClass, OpenApiSecurityScheme security codeClass.AddProperty( new CodeProperty { - Name = "", Type = new CodeType { Name = Authentication.Basic.ToString(), IsExternal = true }, Kind = CodePropertyKind.Headers, DefaultValue = $"{securityScheme.Scheme}Auth" @@ -615,7 +614,6 @@ private void AddApiKeySecurity(CodeClass codeClass, OpenApiSecurityScheme securi codeClass.AddProperty( new CodeProperty { - Name = "", Type = new CodeType { Name = Authentication.APIKey.ToString(), IsExternal = true }, Kind = CodePropertyKind.Headers, DefaultValue = $"{securityScheme.Scheme}Auth" @@ -628,7 +626,6 @@ private void AddOAuth2Security(CodeClass codeClass, OpenApiSecurityScheme securi codeClass.AddProperty( new CodeProperty { - Name = "bearer", Type = new CodeType { Name = Authentication.OAuthV2.ToString(), IsExternal = true }, Kind = CodePropertyKind.Headers, DefaultValue = $"{securityScheme.Scheme}Auth" diff --git a/src/Kiota.Builder/Writers/HTTP/Authentication.cs b/src/Kiota.Builder/Writers/HTTP/Authentication.cs index dd0287aad5..5d2ad8eb9c 100644 --- a/src/Kiota.Builder/Writers/HTTP/Authentication.cs +++ b/src/Kiota.Builder/Writers/HTTP/Authentication.cs @@ -1,10 +1,8 @@ namespace Kiota.Builder.Writers; public enum Authentication { - None, Basic, Bearer, OAuthV2, APIKey, - Custom } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 1e8603886b..fc42e1a366 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -233,8 +233,8 @@ private static void WriteHttpMethods( var authenticationMethod = requestBuilderClass .Properties .FirstOrDefault(static prop => prop.IsOfKind(CodePropertyKind.Headers)); - - if (authenticationMethod != null && authenticationMethod.Name.Equals("authentication-scheme", StringComparison.OrdinalIgnoreCase)) { + + if (authenticationMethod != null && Enum.IsDefined(typeof(Authentication), authenticationMethod.Type.Name)){ writer.WriteLine($"Authorization: {{{{{authenticationMethod.DefaultValue}}}}}"); } From 3155aa55e1ccde91064ca02b14e0596ba8822cd7 Mon Sep 17 00:00:00 2001 From: Onokaev Date: Thu, 16 Jan 2025 12:02:57 +0300 Subject: [PATCH 40/66] Trim settings management service --- .../Settings/ISettingsManagementService.cs | 20 ++--------- .../Settings/SettingsFileManagementService.cs | 33 ++++++++++--------- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/src/Kiota.Builder/Settings/ISettingsManagementService.cs b/src/Kiota.Builder/Settings/ISettingsManagementService.cs index 339e55ee4c..4b6104b51e 100644 --- a/src/Kiota.Builder/Settings/ISettingsManagementService.cs +++ b/src/Kiota.Builder/Settings/ISettingsManagementService.cs @@ -1,8 +1,6 @@ -using System.IO; -using System.Threading; +using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Models; -using Microsoft.OpenApi.Services; namespace Kiota.Builder.Settings; /// @@ -15,21 +13,7 @@ public interface ISettingsManagementService /// /// /// - string GetDirectoryContainingSettingsFile(string searchDirectory); - - /// - /// Gets the settings from a directory. - /// - /// - /// - Task GetSettingsFromDirectoryAsync(string directoryPath, CancellationToken cancellationToken); - - /// - /// Gets the settings from a stream. - /// - /// - /// - Task GetSettingsFromStreamAsync(Stream stream); + string? GetDirectoryContainingSettingsFile(string searchDirectory); /// /// Writes the settings file to a directory. diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index 33e31f8de5..ea882fd2ad 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -10,19 +10,21 @@ namespace Kiota.Builder.Settings; public class SettingsFileManagementService : ISettingsManagementService { internal const string SettingsFileName = "settings.json"; - public string GetDirectoryContainingSettingsFile(string searchDirectory) + public string? GetDirectoryContainingSettingsFile(string searchDirectory) { - throw new NotImplementedException(); - } + var currentDirectory = new DirectoryInfo(searchDirectory); - public Task GetSettingsFromDirectoryAsync(string directoryPath, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + while (currentDirectory != null) + { + var vscodeDirectoryPath = Path.Combine(currentDirectory.FullName, ".vscode"); + if (Directory.Exists(vscodeDirectoryPath)) + { + return vscodeDirectoryPath; + } + currentDirectory = currentDirectory.Parent; + } - public Task GetSettingsFromStreamAsync(Stream stream) - { - throw new NotImplementedException(); + return null; } public Task WriteSettingsFileAsync(string directoryPath, OpenApiDocument openApiDocument, CancellationToken cancellationToken) @@ -49,18 +51,17 @@ private static SettingsFile GenerateSettingsFile(OpenApiDocument openApiDocument private static readonly SettingsFileGenerationContext context = new(options); - private static async Task WriteSettingsFileInternalAsync(string directoryPath, SettingsFile settings, CancellationToken cancellationToken) + private async Task WriteSettingsFileInternalAsync(string directoryPath, SettingsFile settings, CancellationToken cancellationToken) { var parentDirectoryPath = Path.GetDirectoryName(directoryPath); - var vscodeDirectoryPath = Path.Combine(parentDirectoryPath!, ".vscode"); + var vscodeDirectoryPath = GetDirectoryContainingSettingsFile(parentDirectoryPath!); if (!Directory.Exists(vscodeDirectoryPath)) { - Directory.CreateDirectory(vscodeDirectoryPath); + Directory.CreateDirectory(".vscode"); } - var filePath = Path.Combine(vscodeDirectoryPath, SettingsFileName); - // var filePath = Path.Combine(directoryPath, SettingsFileName); -#pragma warning disable CA2007 // Dispose objects before losing scope + var filePath = Path.Combine(vscodeDirectoryPath!, SettingsFileName); +#pragma warning disable CA2007 await using var fileStream = File.Open(filePath, FileMode.Create); #pragma warning disable CA2007 await JsonSerializer.SerializeAsync(fileStream, settings, context.SettingsFile, cancellationToken).ConfigureAwait(false); From b531fc7da91dd75cec2fb6d38eaaa1657cd2de10 Mon Sep 17 00:00:00 2001 From: Onokaev Date: Thu, 16 Jan 2025 17:39:04 +0300 Subject: [PATCH 41/66] Update settings file or write a new one --- .../Settings/ISettingsManagementService.cs | 4 +- .../Settings/SettingsFileGenerationContext.cs | 6 +- .../Settings/SettingsFileManagementService.cs | 68 +++++++++++++++---- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/Kiota.Builder/Settings/ISettingsManagementService.cs b/src/Kiota.Builder/Settings/ISettingsManagementService.cs index 4b6104b51e..a7b719240b 100644 --- a/src/Kiota.Builder/Settings/ISettingsManagementService.cs +++ b/src/Kiota.Builder/Settings/ISettingsManagementService.cs @@ -1,6 +1,8 @@ -using System.Threading; +using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Services; namespace Kiota.Builder.Settings; /// diff --git a/src/Kiota.Builder/Settings/SettingsFileGenerationContext.cs b/src/Kiota.Builder/Settings/SettingsFileGenerationContext.cs index d5dcb10899..8b4ea3ba01 100644 --- a/src/Kiota.Builder/Settings/SettingsFileGenerationContext.cs +++ b/src/Kiota.Builder/Settings/SettingsFileGenerationContext.cs @@ -1,12 +1,14 @@ using System; +using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; -using Kiota.Builder.Lock; namespace Kiota.Builder.Settings; [JsonSerializable(typeof(SettingsFile))] +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(Dictionary))] internal partial class SettingsFileGenerationContext : JsonSerializerContext { } diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index ea882fd2ad..c8a81c6a03 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text.Json; using System.Threading; @@ -10,6 +11,7 @@ namespace Kiota.Builder.Settings; public class SettingsFileManagementService : ISettingsManagementService { internal const string SettingsFileName = "settings.json"; + internal const string EnvironmentVariablesKey = "rest-client.environmentVariables"; public string? GetDirectoryContainingSettingsFile(string searchDirectory) { var currentDirectory = new DirectoryInfo(searchDirectory); @@ -43,27 +45,65 @@ private static SettingsFile GenerateSettingsFile(OpenApiDocument openApiDocument return settings; } - private static readonly JsonSerializerOptions options = new() - { - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - - private static readonly SettingsFileGenerationContext context = new(options); - private async Task WriteSettingsFileInternalAsync(string directoryPath, SettingsFile settings, CancellationToken cancellationToken) { + var vsCodeDirectoryName = ".vscode"; var parentDirectoryPath = Path.GetDirectoryName(directoryPath); var vscodeDirectoryPath = GetDirectoryContainingSettingsFile(parentDirectoryPath!); if (!Directory.Exists(vscodeDirectoryPath)) { - Directory.CreateDirectory(".vscode"); + Directory.CreateDirectory(vsCodeDirectoryName); } + vscodeDirectoryPath = Path.Combine(parentDirectoryPath!, vsCodeDirectoryName); + var settingsObjectString = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.SettingsFile); + + VsCodeSettingsManager settingsManager = new(vscodeDirectoryPath, SettingsFileName); + await settingsManager.UpdateSettingAsync(settingsObjectString, EnvironmentVariablesKey, cancellationToken).ConfigureAwait(false); + } +} + +public class VsCodeSettingsManager +{ + private readonly string _vscodePath; + private readonly string _settingsPath; + + public VsCodeSettingsManager(string basePath, string settingKey) + { + _vscodePath = basePath; + _settingsPath = Path.Combine(_vscodePath, settingKey); + } + + public async Task UpdateSettingAsync(string setting, string settingKey, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrEmpty(setting); + Dictionary settings; + + // Read existing settings or create new if file doesn't exist + if (File.Exists(_settingsPath)) + { + string jsonContent = await File.ReadAllTextAsync(_settingsPath, cancellationToken).ConfigureAwait(false); + try + { + settings = JsonSerializer.Deserialize( + jsonContent, + SettingsFileGenerationContext.Default.DictionaryStringObject) + ?? []; + } + catch (JsonException) + { + settings = []; + } + } + else + { + settings = []; + } + + var settingJsonString = JsonSerializer.Deserialize>(setting, SettingsFileGenerationContext.Default.DictionaryStringObject); + if(settingJsonString is not null) + settings[settingKey] = settingJsonString[settingKey]; - var filePath = Path.Combine(vscodeDirectoryPath!, SettingsFileName); -#pragma warning disable CA2007 - await using var fileStream = File.Open(filePath, FileMode.Create); -#pragma warning disable CA2007 - await JsonSerializer.SerializeAsync(fileStream, settings, context.SettingsFile, cancellationToken).ConfigureAwait(false); + string updatedJson = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.DictionaryStringObject); + await File.WriteAllTextAsync(_settingsPath, updatedJson, cancellationToken).ConfigureAwait(false); } } From b97613aa2b484354f6577209d1be65c08142c6e7 Mon Sep 17 00:00:00 2001 From: Onokaev Date: Thu, 16 Jan 2025 17:39:55 +0300 Subject: [PATCH 42/66] Rename symbol --- src/Kiota.Builder/Settings/SettingsFileManagementService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index c8a81c6a03..c24a691825 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -58,7 +58,7 @@ private async Task WriteSettingsFileInternalAsync(string directoryPath, Settings var settingsObjectString = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.SettingsFile); VsCodeSettingsManager settingsManager = new(vscodeDirectoryPath, SettingsFileName); - await settingsManager.UpdateSettingAsync(settingsObjectString, EnvironmentVariablesKey, cancellationToken).ConfigureAwait(false); + await settingsManager.UpdateFileAsync(settingsObjectString, EnvironmentVariablesKey, cancellationToken).ConfigureAwait(false); } } @@ -73,7 +73,7 @@ public VsCodeSettingsManager(string basePath, string settingKey) _settingsPath = Path.Combine(_vscodePath, settingKey); } - public async Task UpdateSettingAsync(string setting, string settingKey, CancellationToken cancellationToken) + public async Task UpdateFileAsync(string setting, string settingKey, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrEmpty(setting); Dictionary settings; From 2103872a4c9d917ddfac886e6e6ccc61ba59425a Mon Sep 17 00:00:00 2001 From: Onokaev Date: Thu, 16 Jan 2025 17:55:08 +0300 Subject: [PATCH 43/66] Fix formatting --- src/Kiota.Builder/Settings/SettingsFile.cs | 4 ++-- .../Settings/SettingsFileManagementService.cs | 24 +++++++++---------- .../Writers/HTTP/Authentication.cs | 4 ++-- .../HTTP/CodeClassDeclarationWriter.cs | 3 ++- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Kiota.Builder/Settings/SettingsFile.cs b/src/Kiota.Builder/Settings/SettingsFile.cs index 30475af77b..32d989e3ee 100644 --- a/src/Kiota.Builder/Settings/SettingsFile.cs +++ b/src/Kiota.Builder/Settings/SettingsFile.cs @@ -32,8 +32,8 @@ public AuthenticationSettings Remote public AuthenticationSettings Development { get; set; - } - + } + public EnvironmentVariables() { Shared = new SharedAuth(); diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index c24a691825..f3d31c5342 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -65,28 +65,28 @@ private async Task WriteSettingsFileInternalAsync(string directoryPath, Settings public class VsCodeSettingsManager { private readonly string _vscodePath; - private readonly string _settingsPath; + private readonly string fileUpdatePath; - public VsCodeSettingsManager(string basePath, string settingKey) + public VsCodeSettingsManager(string basePath, string targetFilePath) { _vscodePath = basePath; - _settingsPath = Path.Combine(_vscodePath, settingKey); + fileUpdatePath = Path.Combine(_vscodePath, targetFilePath); } - public async Task UpdateFileAsync(string setting, string settingKey, CancellationToken cancellationToken) + public async Task UpdateFileAsync(string fileUpdate, string fileUpdateKey, CancellationToken cancellationToken) { - ArgumentException.ThrowIfNullOrEmpty(setting); + ArgumentException.ThrowIfNullOrEmpty(fileUpdate); Dictionary settings; // Read existing settings or create new if file doesn't exist - if (File.Exists(_settingsPath)) + if (File.Exists(fileUpdatePath)) { - string jsonContent = await File.ReadAllTextAsync(_settingsPath, cancellationToken).ConfigureAwait(false); + string jsonContent = await File.ReadAllTextAsync(fileUpdatePath, cancellationToken).ConfigureAwait(false); try { settings = JsonSerializer.Deserialize( jsonContent, - SettingsFileGenerationContext.Default.DictionaryStringObject) + SettingsFileGenerationContext.Default.DictionaryStringObject) ?? []; } catch (JsonException) @@ -99,11 +99,11 @@ public async Task UpdateFileAsync(string setting, string settingKey, Cancellatio settings = []; } - var settingJsonString = JsonSerializer.Deserialize>(setting, SettingsFileGenerationContext.Default.DictionaryStringObject); - if(settingJsonString is not null) - settings[settingKey] = settingJsonString[settingKey]; + var fileUpdateDictionary = JsonSerializer.Deserialize>(fileUpdate, SettingsFileGenerationContext.Default.DictionaryStringObject); + if (fileUpdateDictionary is not null) + settings[fileUpdateKey] = fileUpdateDictionary[fileUpdateKey]; string updatedJson = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.DictionaryStringObject); - await File.WriteAllTextAsync(_settingsPath, updatedJson, cancellationToken).ConfigureAwait(false); + await File.WriteAllTextAsync(fileUpdatePath, updatedJson, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Kiota.Builder/Writers/HTTP/Authentication.cs b/src/Kiota.Builder/Writers/HTTP/Authentication.cs index 5d2ad8eb9c..4a486f91ba 100644 --- a/src/Kiota.Builder/Writers/HTTP/Authentication.cs +++ b/src/Kiota.Builder/Writers/HTTP/Authentication.cs @@ -1,5 +1,5 @@ -namespace Kiota.Builder.Writers; -public enum Authentication +namespace Kiota.Builder.Writers; +public enum Authentication { Basic, Bearer, diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index fc42e1a366..41c5a7077e 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -234,7 +234,8 @@ private static void WriteHttpMethods( .Properties .FirstOrDefault(static prop => prop.IsOfKind(CodePropertyKind.Headers)); - if (authenticationMethod != null && Enum.IsDefined(typeof(Authentication), authenticationMethod.Type.Name)){ + if (authenticationMethod != null && Enum.IsDefined(typeof(Authentication), authenticationMethod.Type.Name)) + { writer.WriteLine($"Authorization: {{{{{authenticationMethod.DefaultValue}}}}}"); } From 37d2e6e682553068b4a54ff5cf3bd13fd2d0da28 Mon Sep 17 00:00:00 2001 From: Onokaev Date: Thu, 16 Jan 2025 18:35:40 +0300 Subject: [PATCH 44/66] Add test --- .../Settings/SettingsFileManagementService.cs | 17 ++- .../Settings/SettingsManagementTests.cs | 103 ++++++++++++++++++ 2 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index f3d31c5342..8bf2c7f711 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -15,17 +15,11 @@ public class SettingsFileManagementService : ISettingsManagementService public string? GetDirectoryContainingSettingsFile(string searchDirectory) { var currentDirectory = new DirectoryInfo(searchDirectory); - - while (currentDirectory != null) + var vscodeDirectoryPath = Path.Combine(currentDirectory.FullName, ".vscode"); + if (Directory.Exists(vscodeDirectoryPath)) { - var vscodeDirectoryPath = Path.Combine(currentDirectory.FullName, ".vscode"); - if (Directory.Exists(vscodeDirectoryPath)) - { - return vscodeDirectoryPath; - } - currentDirectory = currentDirectory.Parent; + return vscodeDirectoryPath; } - return null; } @@ -78,6 +72,11 @@ public async Task UpdateFileAsync(string fileUpdate, string fileUpdateKey, Cance ArgumentException.ThrowIfNullOrEmpty(fileUpdate); Dictionary settings; + if (!Directory.Exists(fileUpdatePath)) + { + Directory.CreateDirectory(fileUpdatePath); + } + // Read existing settings or create new if file doesn't exist if (File.Exists(fileUpdatePath)) { diff --git a/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs new file mode 100644 index 0000000000..7047d1fccc --- /dev/null +++ b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.OpenApi.Models; +using Xunit; + +namespace Kiota.Builder.Settings.Tests +{ + public class SettingsFileManagementServiceTest + { + [Fact] + public void GetDirectoryContainingSettingsFile_ShouldReturnNull_WhenNoVscodeDirectoryExists() + { + // Arrange + var service = new SettingsFileManagementService(); + var tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDirectory); + + // Act + var result = service.GetDirectoryContainingSettingsFile(tempDirectory); + + // Assert + Assert.Null(result); + + // Cleanup + Directory.Delete(tempDirectory); + } + + [Fact] + public void GetDirectoryContainingSettingsFile_ShouldReturnVscodeDirectory_WhenExists() + { + // Arrange + var service = new SettingsFileManagementService(); + var tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var vscodeDirectory = Path.Combine(tempDirectory, ".vscode"); + Directory.CreateDirectory(vscodeDirectory); + + // Act + var result = service.GetDirectoryContainingSettingsFile(tempDirectory); + + // Assert + Assert.Equal(vscodeDirectory, result); + + // Cleanup + Directory.Delete(tempDirectory, true); + } + + [Fact] + public async Task WriteSettingsFileAsync_ShouldThrowArgumentException_WhenDirectoryPathIsNullOrEmpty() + { + // Arrange + var service = new SettingsFileManagementService(); + var openApiDocument = new OpenApiDocument(); + var cancellationToken = CancellationToken.None; + + // Act & Assert + await Assert.ThrowsAsync(() => service.WriteSettingsFileAsync(string.Empty, openApiDocument, cancellationToken)); + } + + [Fact] + public async Task WriteSettingsFileAsync_ShouldThrowArgumentNullException_WhenOpenApiDocumentIsNull() + { + // Arrange + var service = new SettingsFileManagementService(); + var tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDirectory); + var cancellationToken = CancellationToken.None; + + // Act & Assert + await Assert.ThrowsAsync(() => service.WriteSettingsFileAsync(tempDirectory, null, cancellationToken)); + + // Cleanup + Directory.Delete(tempDirectory); + } + + [Fact] + public async Task WriteSettingsFileAsync_ShouldCreateSettingsFile_WhenValidInputs() + { + // Arrange + var service = new SettingsFileManagementService(); + var tempDirectory = Path.GetTempPath(); + Directory.CreateDirectory(tempDirectory); + var openApiDocument = new OpenApiDocument + { + Servers = new List { new OpenApiServer { Url = "http://localhost" } } + }; + var cancellationToken = CancellationToken.None; + + // Act + await service.WriteSettingsFileAsync(tempDirectory, openApiDocument, cancellationToken); + + // Assert + var vscodeDirectory = Path.Combine(tempDirectory, ".vscode"); + var settingsFilePath = Path.Combine(vscodeDirectory, SettingsFileManagementService.SettingsFileName); + Assert.True(File.Exists(settingsFilePath)); + + // Cleanup + Directory.Delete(tempDirectory, true); + } + } +} From f316a5cc49a21fbcfeffabdb0603e5d721f29d8e Mon Sep 17 00:00:00 2001 From: Onokaev Date: Thu, 16 Jan 2025 19:04:59 +0300 Subject: [PATCH 45/66] Update write path --- .../Settings/SettingsFileManagementService.cs | 14 +++++------ .../Settings/SettingsManagementTests.cs | 25 ------------------- 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index 8bf2c7f711..8fb19eb54b 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Runtime.Serialization.Formatters.Binary; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -46,7 +47,8 @@ private async Task WriteSettingsFileInternalAsync(string directoryPath, Settings var vscodeDirectoryPath = GetDirectoryContainingSettingsFile(parentDirectoryPath!); if (!Directory.Exists(vscodeDirectoryPath)) { - Directory.CreateDirectory(vsCodeDirectoryName); + var pathToWrite = Path.Combine(parentDirectoryPath!, vsCodeDirectoryName); + Directory.CreateDirectory(pathToWrite); } vscodeDirectoryPath = Path.Combine(parentDirectoryPath!, vsCodeDirectoryName); var settingsObjectString = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.SettingsFile); @@ -72,11 +74,6 @@ public async Task UpdateFileAsync(string fileUpdate, string fileUpdateKey, Cance ArgumentException.ThrowIfNullOrEmpty(fileUpdate); Dictionary settings; - if (!Directory.Exists(fileUpdatePath)) - { - Directory.CreateDirectory(fileUpdatePath); - } - // Read existing settings or create new if file doesn't exist if (File.Exists(fileUpdatePath)) { @@ -102,7 +99,8 @@ public async Task UpdateFileAsync(string fileUpdate, string fileUpdateKey, Cance if (fileUpdateDictionary is not null) settings[fileUpdateKey] = fileUpdateDictionary[fileUpdateKey]; - string updatedJson = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.DictionaryStringObject); - await File.WriteAllTextAsync(fileUpdatePath, updatedJson, cancellationToken).ConfigureAwait(false); +#pragma warning disable CA2007 + await using var fileStream = File.Open(fileUpdatePath, FileMode.Create); + await JsonSerializer.SerializeAsync(fileStream, settings, SettingsFileGenerationContext.Default.DictionaryStringObject, cancellationToken).ConfigureAwait(false); } } diff --git a/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs index 7047d1fccc..78152be5af 100644 --- a/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs +++ b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs @@ -74,30 +74,5 @@ public async Task WriteSettingsFileAsync_ShouldThrowArgumentNullException_WhenOp // Cleanup Directory.Delete(tempDirectory); } - - [Fact] - public async Task WriteSettingsFileAsync_ShouldCreateSettingsFile_WhenValidInputs() - { - // Arrange - var service = new SettingsFileManagementService(); - var tempDirectory = Path.GetTempPath(); - Directory.CreateDirectory(tempDirectory); - var openApiDocument = new OpenApiDocument - { - Servers = new List { new OpenApiServer { Url = "http://localhost" } } - }; - var cancellationToken = CancellationToken.None; - - // Act - await service.WriteSettingsFileAsync(tempDirectory, openApiDocument, cancellationToken); - - // Assert - var vscodeDirectory = Path.Combine(tempDirectory, ".vscode"); - var settingsFilePath = Path.Combine(vscodeDirectory, SettingsFileManagementService.SettingsFileName); - Assert.True(File.Exists(settingsFilePath)); - - // Cleanup - Directory.Delete(tempDirectory, true); - } } } From e6a542c09f9ad172811054ac7339d1f9075f5067 Mon Sep 17 00:00:00 2001 From: Onokaev Date: Thu, 16 Jan 2025 19:15:00 +0300 Subject: [PATCH 46/66] Move Authentication enum to constants --- src/Kiota.Builder/Constants.cs | 8 ++++++++ src/Kiota.Builder/Writers/HTTP/Authentication.cs | 8 -------- .../Settings/SettingsManagementTests.cs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 src/Kiota.Builder/Writers/HTTP/Authentication.cs diff --git a/src/Kiota.Builder/Constants.cs b/src/Kiota.Builder/Constants.cs index 1968837aa6..c0589d425a 100644 --- a/src/Kiota.Builder/Constants.cs +++ b/src/Kiota.Builder/Constants.cs @@ -4,3 +4,11 @@ public static class Constants public const string DefaultOpenApiLabel = "default"; public const string TempDirectoryName = "kiota"; } + +public enum Authentication +{ + Basic, + Bearer, + OAuthV2, + APIKey, +} diff --git a/src/Kiota.Builder/Writers/HTTP/Authentication.cs b/src/Kiota.Builder/Writers/HTTP/Authentication.cs deleted file mode 100644 index 4a486f91ba..0000000000 --- a/src/Kiota.Builder/Writers/HTTP/Authentication.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Kiota.Builder.Writers; -public enum Authentication -{ - Basic, - Bearer, - OAuthV2, - APIKey, -} diff --git a/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs index 78152be5af..d49c5d7bcf 100644 --- a/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs +++ b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Threading; From 926759e85b4a8be8ba572aa2d216ccbff95ae9ee Mon Sep 17 00:00:00 2001 From: Onokaev Date: Thu, 16 Jan 2025 19:21:07 +0300 Subject: [PATCH 47/66] Add JsonProperty names for setting file vars --- src/Kiota.Builder/Settings/SettingsFile.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/Settings/SettingsFile.cs b/src/Kiota.Builder/Settings/SettingsFile.cs index 32d989e3ee..03ef3491c1 100644 --- a/src/Kiota.Builder/Settings/SettingsFile.cs +++ b/src/Kiota.Builder/Settings/SettingsFile.cs @@ -24,16 +24,18 @@ public SharedAuth Shared get; set; } + [JsonPropertyName("remote")] public AuthenticationSettings Remote { get; set; } + [JsonPropertyName("development")] public AuthenticationSettings Development { get; set; - } - + } + public EnvironmentVariables() { Shared = new SharedAuth(); @@ -49,21 +51,25 @@ public class SharedAuth public class AuthenticationSettings { + [JsonPropertyName("hostAddress")] public string HostAddress { get; set; } + [JsonPropertyName("basicAuth")] public string? BasicAuth { get; set; } + [JsonPropertyName("bearerAuth")] public string? BearerAuth { get; set; } + [JsonPropertyName("apiKeyAuth")] public string? ApiKey { get; set; From 5b8c189b4e6aa3328b785b81700fd53a7972babd Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Fri, 17 Jan 2025 11:48:04 +0300 Subject: [PATCH 48/66] Update tests to remove assertions for baseUrl in code files --- src/Kiota.Builder/KiotaBuilder.cs | 7 ++- src/Kiota.Builder/Settings/SettingsFile.cs | 3 + .../HTTP/CodeClassDeclarationWriter.cs | 6 -- .../HTTP/CodeClassDeclarationWriterTests.cs | 59 +------------------ 4 files changed, 11 insertions(+), 64 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index b936cb4333..b25888556a 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -561,7 +561,7 @@ public CodeNamespace CreateSourceModel(OpenApiUrlTreeNode? root) return rootNamespace; } - private void ProcessSecurityRequirements(OpenApiOperation operation, CodeClass codeClass) + private void AddOperationSecurityRequirementToDOM(OpenApiOperation operation, CodeClass codeClass) { if (openApiDocument is null) { @@ -753,7 +753,10 @@ private void CreateRequestBuilderClass(CodeNamespace currentNamespace, OpenApiUr { CreateOperationMethods(currentNode, operation.Key, operation.Value, codeClass); - ProcessSecurityRequirements(operation.Value, codeClass); + if (config.Language == GenerationLanguage.HTTP) + { + AddOperationSecurityRequirementToDOM(operation.Value, codeClass); + } } } diff --git a/src/Kiota.Builder/Settings/SettingsFile.cs b/src/Kiota.Builder/Settings/SettingsFile.cs index 03ef3491c1..735c0e1110 100644 --- a/src/Kiota.Builder/Settings/SettingsFile.cs +++ b/src/Kiota.Builder/Settings/SettingsFile.cs @@ -78,5 +78,8 @@ public string? ApiKey public AuthenticationSettings() { HostAddress = ""; + BasicAuth = ""; + BearerAuth = ""; + ApiKey = ""; } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 41c5a7077e..825ce73bbe 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -29,12 +29,6 @@ protected override void WriteTypeDeclaration(ClassDeclaration codeElement, Langu var baseUrl = GetBaseUrl(requestBuilderClass); - // Write the baseUrl variable - //WriteBaseUrl(baseUrl, writer); - - // Extract and write the URL template - WriteUrlTemplate(urlTemplateProperty, writer); - // Write path parameters WritePathParameters(pathParameters, writer); diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs index adb79e9105..5e4dbd3457 100644 --- a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeClassDeclarationWriterTests.cs @@ -38,59 +38,6 @@ public void Dispose() GC.SuppressFinalize(this); } - [Fact] - public async Task WritesBaseUrlProperty() - { - var codeClass = new CodeClass - { - Name = "TestClass", - Kind = CodeClassKind.RequestBuilder - }; - var urlTemplateProperty = new CodeProperty - { - Name = "urlTemplate", - Kind = CodePropertyKind.UrlTemplate, - DefaultValue = "\"https://example.com/{id}\"", - Type = new CodeType - { - Name = "string", - IsExternal = true - }, - }; - codeClass.AddProperty(urlTemplateProperty); - - // Add base url property - var baseUrlProperty = new CodeProperty - { - Name = "BaseUrl", - Kind = CodePropertyKind.Custom, - Access = AccessModifier.Private, - DefaultValue = "https://example.com", - Type = new CodeType { Name = "string", IsExternal = true } - }; - codeClass.AddProperty(baseUrlProperty); - - var method = new CodeMethod - { - Name = "get", - Kind = CodeMethodKind.RequestExecutor, - Documentation = new CodeDocumentation { DescriptionTemplate = "GET method" }, - ReturnType = new CodeType { Name = "void" } - }; - - codeClass.AddMethod(method); - - root.AddClass(codeClass); - - await ILanguageRefiner.RefineAsync(new GenerationConfiguration { Language = GenerationLanguage.HTTP }, root); - - writer.Write(codeClass.StartBlock); - var result = tw.ToString(); - - Assert.Contains("# Base url for the server/host", result); - Assert.Contains("@url = https://example.com", result); - } - [Fact] public async Task WritesRequestExecutorMethods() { @@ -211,9 +158,9 @@ public async Task WritesRequestExecutorMethods() var result = tw.ToString(); // Check HTTP operations - Assert.Contains("GET {{url}}/posts HTTP/1.1", result); - Assert.Contains("PATCH {{url}}/posts HTTP/1.1", result); - Assert.Contains("POST {{url}}/posts HTTP/1.1", result); + Assert.Contains("GET {{hostAddress}}/posts HTTP/1.1", result); + Assert.Contains("PATCH {{hostAddress}}/posts HTTP/1.1", result); + Assert.Contains("POST {{hostAddress}}/posts HTTP/1.1", result); // Check content type Assert.Contains("Content-Type: application/json", result); From 7693fcf48ffd91009adac692009eb9ac63434ac2 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Fri, 17 Jan 2025 11:56:56 +0300 Subject: [PATCH 49/66] Refactor GetDirectoryContainingSettingsFile --- .../Settings/SettingsFileManagementService.cs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index 8fb19eb54b..94a1e9aae0 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -13,15 +13,17 @@ public class SettingsFileManagementService : ISettingsManagementService { internal const string SettingsFileName = "settings.json"; internal const string EnvironmentVariablesKey = "rest-client.environmentVariables"; - public string? GetDirectoryContainingSettingsFile(string searchDirectory) + internal const string VsCodeDirectoryName = ".vscode"; + public string GetDirectoryContainingSettingsFile(string searchDirectory) { var currentDirectory = new DirectoryInfo(searchDirectory); - var vscodeDirectoryPath = Path.Combine(currentDirectory.FullName, ".vscode"); + var vscodeDirectoryPath = Path.Combine(currentDirectory.FullName, VsCodeDirectoryName); if (Directory.Exists(vscodeDirectoryPath)) { return vscodeDirectoryPath; } - return null; + var pathToWrite = Path.Combine(searchDirectory, VsCodeDirectoryName); + return Directory.CreateDirectory(pathToWrite).FullName; } public Task WriteSettingsFileAsync(string directoryPath, OpenApiDocument openApiDocument, CancellationToken cancellationToken) @@ -42,17 +44,9 @@ private static SettingsFile GenerateSettingsFile(OpenApiDocument openApiDocument private async Task WriteSettingsFileInternalAsync(string directoryPath, SettingsFile settings, CancellationToken cancellationToken) { - var vsCodeDirectoryName = ".vscode"; var parentDirectoryPath = Path.GetDirectoryName(directoryPath); var vscodeDirectoryPath = GetDirectoryContainingSettingsFile(parentDirectoryPath!); - if (!Directory.Exists(vscodeDirectoryPath)) - { - var pathToWrite = Path.Combine(parentDirectoryPath!, vsCodeDirectoryName); - Directory.CreateDirectory(pathToWrite); - } - vscodeDirectoryPath = Path.Combine(parentDirectoryPath!, vsCodeDirectoryName); var settingsObjectString = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.SettingsFile); - VsCodeSettingsManager settingsManager = new(vscodeDirectoryPath, SettingsFileName); await settingsManager.UpdateFileAsync(settingsObjectString, EnvironmentVariablesKey, cancellationToken).ConfigureAwait(false); } From 58a31e284612ccc7daeef166268273be8b370cd9 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Fri, 17 Jan 2025 12:03:17 +0300 Subject: [PATCH 50/66] Update test --- .../Settings/SettingsManagementTests.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs index d49c5d7bcf..1b2c1380a6 100644 --- a/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs +++ b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs @@ -11,7 +11,7 @@ namespace Kiota.Builder.Settings.Tests public class SettingsFileManagementServiceTest { [Fact] - public void GetDirectoryContainingSettingsFile_ShouldReturnNull_WhenNoVscodeDirectoryExists() + public void GetDirectoryContainingSettingsFile_ShouldCreateTheDirectory_If_It_Doesnt_Exist() { // Arrange var service = new SettingsFileManagementService(); @@ -22,10 +22,18 @@ public void GetDirectoryContainingSettingsFile_ShouldReturnNull_WhenNoVscodeDire var result = service.GetDirectoryContainingSettingsFile(tempDirectory); // Assert - Assert.Null(result); + Assert.Equal(tempDirectory + "\\.vscode", result); // Cleanup - Directory.Delete(tempDirectory); + try + { + Directory.Delete(tempDirectory, true); + } + catch (IOException) + { + // Handle the case where the directory is not empty + Directory.Delete(tempDirectory, true); + } } [Fact] From e89c51f5132b1641c3219542de20b1a26d2e1af2 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Fri, 17 Jan 2025 12:10:02 +0300 Subject: [PATCH 51/66] Use Path.Combine to add to path --- tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs index 1b2c1380a6..a6e779915a 100644 --- a/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs +++ b/tests/Kiota.Builder.Tests/Settings/SettingsManagementTests.cs @@ -20,9 +20,9 @@ public void GetDirectoryContainingSettingsFile_ShouldCreateTheDirectory_If_It_Do // Act var result = service.GetDirectoryContainingSettingsFile(tempDirectory); - + tempDirectory = Path.Combine(tempDirectory, ".vscode"); // Assert - Assert.Equal(tempDirectory + "\\.vscode", result); + Assert.Equal(tempDirectory, result); // Cleanup try From 33d06ae12288567392db4550c851c6381c416847 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Fri, 17 Jan 2025 15:18:43 +0300 Subject: [PATCH 52/66] Refactor default values for security values --- src/Kiota.Builder/KiotaBuilder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index b25888556a..418d8b20dd 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -604,7 +604,7 @@ private void AddHttpSecurity(CodeClass codeClass, OpenApiSecurityScheme security { Type = new CodeType { Name = Authentication.Basic.ToString(), IsExternal = true }, Kind = CodePropertyKind.Headers, - DefaultValue = $"{securityScheme.Scheme}Auth" + DefaultValue = "basicAuth" } ); } @@ -616,7 +616,7 @@ private void AddApiKeySecurity(CodeClass codeClass, OpenApiSecurityScheme securi { Type = new CodeType { Name = Authentication.APIKey.ToString(), IsExternal = true }, Kind = CodePropertyKind.Headers, - DefaultValue = $"{securityScheme.Scheme}Auth" + DefaultValue = "apiKeyAuth" } ); } @@ -628,7 +628,7 @@ private void AddOAuth2Security(CodeClass codeClass, OpenApiSecurityScheme securi { Type = new CodeType { Name = Authentication.OAuthV2.ToString(), IsExternal = true }, Kind = CodePropertyKind.Headers, - DefaultValue = $"{securityScheme.Scheme}Auth" + DefaultValue = "bearerAuth" } ); } From 05d90977edae913748e810bf221e75eff0f247a1 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Mon, 20 Jan 2025 10:37:41 +0300 Subject: [PATCH 53/66] Add settings file generation to generation steps --- src/Kiota.Builder/Constants.cs | 8 --- src/Kiota.Builder/KiotaBuilder.cs | 49 +++---------------- src/Kiota.Builder/Settings/SettingsFile.cs | 8 +-- .../HTTP/CodeClassDeclarationWriter.cs | 13 ++++- 4 files changed, 21 insertions(+), 57 deletions(-) diff --git a/src/Kiota.Builder/Constants.cs b/src/Kiota.Builder/Constants.cs index c0589d425a..1968837aa6 100644 --- a/src/Kiota.Builder/Constants.cs +++ b/src/Kiota.Builder/Constants.cs @@ -4,11 +4,3 @@ public static class Constants public const string DefaultOpenApiLabel = "default"; public const string TempDirectoryName = "kiota"; } - -public enum Authentication -{ - Basic, - Bearer, - OAuthV2, - APIKey, -} diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 418d8b20dd..618b353d3e 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -290,7 +290,9 @@ public async Task GenerateClientAsync(CancellationToken cancellationToken) if (config.Language == GenerationLanguage.HTTP && openApiDocument is not null) { + sw.Start(); await settingsFileManagementService.WriteSettingsFileAsync(config.OutputPath, openApiDocument, cancellationToken).ConfigureAwait(false); + StopLogAndReset(sw, $"step {++stepId} - generated settings file for HTTP authentication - took"); } return stepId; }, cancellationToken).ConfigureAwait(false); @@ -578,57 +580,18 @@ private void AddOperationSecurityRequirementToDOM(OpenApiOperation operation, Co foreach (var scheme in securityRequirement.Keys) { var securityScheme = securitySchemes[scheme.Reference.Id]; - switch (securityScheme.Type) - { - case SecuritySchemeType.Http: - AddHttpSecurity(codeClass, securityScheme); - break; - case SecuritySchemeType.ApiKey: - AddApiKeySecurity(codeClass, securityScheme); - break; - case SecuritySchemeType.OAuth2: - AddOAuth2Security(codeClass, securityScheme); - break; - default: - logger.LogWarning("Unsupported security scheme type: {Type}", securityScheme.Type); - break; - } + AddSecurity(codeClass, securityScheme); } } } - private void AddHttpSecurity(CodeClass codeClass, OpenApiSecurityScheme securityScheme) - { - codeClass.AddProperty( - new CodeProperty - { - Type = new CodeType { Name = Authentication.Basic.ToString(), IsExternal = true }, - Kind = CodePropertyKind.Headers, - DefaultValue = "basicAuth" - } - ); - } - - private void AddApiKeySecurity(CodeClass codeClass, OpenApiSecurityScheme securityScheme) - { - codeClass.AddProperty( - new CodeProperty - { - Type = new CodeType { Name = Authentication.APIKey.ToString(), IsExternal = true }, - Kind = CodePropertyKind.Headers, - DefaultValue = "apiKeyAuth" - } - ); - } - - private void AddOAuth2Security(CodeClass codeClass, OpenApiSecurityScheme securityScheme) + private void AddSecurity(CodeClass codeClass, OpenApiSecurityScheme openApiSecurityScheme) { codeClass.AddProperty( new CodeProperty { - Type = new CodeType { Name = Authentication.OAuthV2.ToString(), IsExternal = true }, - Kind = CodePropertyKind.Headers, - DefaultValue = "bearerAuth" + Type = new CodeType { Name = openApiSecurityScheme.Type.ToString(), IsExternal = true }, + Kind = CodePropertyKind.Headers } ); } diff --git a/src/Kiota.Builder/Settings/SettingsFile.cs b/src/Kiota.Builder/Settings/SettingsFile.cs index 735c0e1110..1a673e01ee 100644 --- a/src/Kiota.Builder/Settings/SettingsFile.cs +++ b/src/Kiota.Builder/Settings/SettingsFile.cs @@ -77,9 +77,9 @@ public string? ApiKey public AuthenticationSettings() { - HostAddress = ""; - BasicAuth = ""; - BearerAuth = ""; - ApiKey = ""; + HostAddress = string.Empty; + BasicAuth = string.Empty; + BearerAuth = string.Empty; + ApiKey = string.Empty; } } diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 825ce73bbe..95549d2447 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -4,6 +4,7 @@ using Kiota.Builder.CodeDOM; using Kiota.Builder.Extensions; using Microsoft.Kiota.Abstractions; +using Microsoft.OpenApi.Models; namespace Kiota.Builder.Writers.Http; public class CodeClassDeclarationWriter(HttpConventionService conventionService) : CodeProprietableBlockDeclarationWriter(conventionService) @@ -228,9 +229,17 @@ private static void WriteHttpMethods( .Properties .FirstOrDefault(static prop => prop.IsOfKind(CodePropertyKind.Headers)); - if (authenticationMethod != null && Enum.IsDefined(typeof(Authentication), authenticationMethod.Type.Name)) + if (authenticationMethod != null && Enum.TryParse(typeof(SecuritySchemeType), authenticationMethod.Type.Name, true, out var _)) { - writer.WriteLine($"Authorization: {{{{{authenticationMethod.DefaultValue}}}}}"); + var schemeTypeMapping = new Dictionary + { + { SecuritySchemeType.ApiKey.ToString().ToLowerInvariant(), "apiKeyAuth" }, + { SecuritySchemeType.Http.ToString().ToLowerInvariant(), "bearerAuth" }, + { SecuritySchemeType.OAuth2.ToString().ToLowerInvariant(), "bearerAuth" }, + { SecuritySchemeType.OpenIdConnect.ToString().ToLowerInvariant(), "bearerAuth" } + }; + var schemeType = schemeTypeMapping[authenticationMethod.Type.Name.ToLowerInvariant()]; + writer.WriteLine($"Authorization: {{{{{schemeType}}}}}"); } // Write the request body if present From 0f84d3b8edce857be6683dae9606d9a50e02f9ec Mon Sep 17 00:00:00 2001 From: Onokaev Date: Mon, 20 Jan 2025 12:24:52 +0300 Subject: [PATCH 54/66] Refactor VsCodeSettings manager --- .../Settings/SettingsFileManagementService.cs | 58 +++---------------- .../Settings/VsCodeFileManagement.cs | 57 ++++++++++++++++++ .../HTTP/CodeClassDeclarationWriter.cs | 52 +++++++---------- 3 files changed, 86 insertions(+), 81 deletions(-) create mode 100644 src/Kiota.Builder/Settings/VsCodeFileManagement.cs diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index 94a1e9aae0..ff8eda864a 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -37,8 +37,11 @@ public Task WriteSettingsFileAsync(string directoryPath, OpenApiDocument openApi private static SettingsFile GenerateSettingsFile(OpenApiDocument openApiDocument) { var settings = new SettingsFile(); - settings.EnvironmentVariables.Development.HostAddress = openApiDocument.Servers[0].Url; - settings.EnvironmentVariables.Remote.HostAddress = openApiDocument.Servers[0].Url; + if(openApiDocument.Servers?.Count > 0) + { + settings.EnvironmentVariables.Development.HostAddress = openApiDocument.Servers[0].Url; + settings.EnvironmentVariables.Remote.HostAddress = openApiDocument.Servers[0].Url; + } return settings; } @@ -47,54 +50,7 @@ private async Task WriteSettingsFileInternalAsync(string directoryPath, Settings var parentDirectoryPath = Path.GetDirectoryName(directoryPath); var vscodeDirectoryPath = GetDirectoryContainingSettingsFile(parentDirectoryPath!); var settingsObjectString = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.SettingsFile); - VsCodeSettingsManager settingsManager = new(vscodeDirectoryPath, SettingsFileName); - await settingsManager.UpdateFileAsync(settingsObjectString, EnvironmentVariablesKey, cancellationToken).ConfigureAwait(false); - } -} - -public class VsCodeSettingsManager -{ - private readonly string _vscodePath; - private readonly string fileUpdatePath; - - public VsCodeSettingsManager(string basePath, string targetFilePath) - { - _vscodePath = basePath; - fileUpdatePath = Path.Combine(_vscodePath, targetFilePath); - } - - public async Task UpdateFileAsync(string fileUpdate, string fileUpdateKey, CancellationToken cancellationToken) - { - ArgumentException.ThrowIfNullOrEmpty(fileUpdate); - Dictionary settings; - - // Read existing settings or create new if file doesn't exist - if (File.Exists(fileUpdatePath)) - { - string jsonContent = await File.ReadAllTextAsync(fileUpdatePath, cancellationToken).ConfigureAwait(false); - try - { - settings = JsonSerializer.Deserialize( - jsonContent, - SettingsFileGenerationContext.Default.DictionaryStringObject) - ?? []; - } - catch (JsonException) - { - settings = []; - } - } - else - { - settings = []; - } - - var fileUpdateDictionary = JsonSerializer.Deserialize>(fileUpdate, SettingsFileGenerationContext.Default.DictionaryStringObject); - if (fileUpdateDictionary is not null) - settings[fileUpdateKey] = fileUpdateDictionary[fileUpdateKey]; - -#pragma warning disable CA2007 - await using var fileStream = File.Open(fileUpdatePath, FileMode.Create); - await JsonSerializer.SerializeAsync(fileStream, settings, SettingsFileGenerationContext.Default.DictionaryStringObject, cancellationToken).ConfigureAwait(false); + var fileUpdatePath = Path.Combine(vscodeDirectoryPath, SettingsFileName); + await VsCodeSettingsManager.UpdateFileAsync(settingsObjectString, EnvironmentVariablesKey, fileUpdatePath, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Kiota.Builder/Settings/VsCodeFileManagement.cs b/src/Kiota.Builder/Settings/VsCodeFileManagement.cs new file mode 100644 index 0000000000..f3529ffcd0 --- /dev/null +++ b/src/Kiota.Builder/Settings/VsCodeFileManagement.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; + +namespace Kiota.Builder.Settings; + +public static class VsCodeSettingsManager +{ + private static readonly JsonSerializerOptions options = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true, + }; + private static readonly SettingsFileGenerationContext context = new(options); + public static async Task UpdateFileAsync(string fileUpdate, string fileUpdateKey, string fileUpdatePath, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrEmpty(fileUpdate); + Dictionary settings; + + // Read existing settings or create new if file doesn't exist + if (File.Exists(fileUpdatePath)) + { + string jsonContent = await File.ReadAllTextAsync(fileUpdatePath, cancellationToken).ConfigureAwait(false); + try + { + settings = JsonSerializer.Deserialize( + jsonContent, + context.DictionaryStringObject) + ?? []; + } + catch (JsonException) + { + settings = []; + } + } + else + { + settings = []; + } + + var fileUpdateDictionary = JsonSerializer.Deserialize>(fileUpdate, context.DictionaryStringObject); + if (fileUpdateDictionary is not null) + { + foreach (var kvp in fileUpdateDictionary) + { + settings[kvp.Key] = kvp.Value; + } + } + +#pragma warning disable CA2007 + await using var fileStream = File.Open(fileUpdatePath, FileMode.Create); + await JsonSerializer.SerializeAsync(fileStream, settings, context.DictionaryStringObject, cancellationToken).ConfigureAwait(false); + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 95549d2447..bb8db13f5a 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -9,7 +9,16 @@ namespace Kiota.Builder.Writers.Http; public class CodeClassDeclarationWriter(HttpConventionService conventionService) : CodeProprietableBlockDeclarationWriter(conventionService) { - private const string BaseUrlPropertyName = "hostAddress"; + private static class Constants + { + internal const string BaseUrlPropertyName = "hostAddress"; + internal const string PathParameters = "pathParameters"; + internal const string BaseUrl = "baseUrl"; + internal const string ApiKeyAuth = "apiKeyAuth"; + internal const string BearerAuth = "bearerAuth"; + internal const string HttpVersion = "HTTP/1.1"; + internal const string LocalHostUrl = "http://localhost/"; + } protected override void WriteTypeDeclaration(ClassDeclaration codeElement, LanguageWriter writer) { @@ -58,12 +67,11 @@ private static List GetAllQueryParameters(CodeClass requestBuilder .ToList(); // Collect all query parameter properties into the aggregated list - queryParameterClasses?.ForEach(paramCodeClass => + queryParameterClasses.ForEach(paramCodeClass => { var queryParams = paramCodeClass .Properties - .Where(static property => property.IsOfKind(CodePropertyKind.QueryParameter)) - .ToList(); + .SelectMany(static property => property.IsOfKind(CodePropertyKind.QueryParameter) ? [property] : Array.Empty()); queryParameters.AddRange(queryParams); }); @@ -82,25 +90,12 @@ private static List GetPathParameters(CodeClass requestBuilderClas var pathParameters = requestBuilderClass .GetChildElements(true) .OfType() - .Where(property => property.IsOfKind(CodePropertyKind.PathParameters) && !property.Name.Equals("pathParameters", StringComparison.OrdinalIgnoreCase)) + .Where(property => property.IsOfKind(CodePropertyKind.PathParameters) && !property.Name.Equals(Constants.PathParameters, StringComparison.OrdinalIgnoreCase)) .ToList(); return pathParameters; } - /// - /// Writes the base URL for the given request builder class to the writer. - /// - /// The request builder class containing the base URL property. - /// The language writer to write the base URL to. - private static void WriteBaseUrl(string? baseUrl, LanguageWriter writer) - { - // Write the base URL variable to the writer - writer.WriteLine($"# Base url for the server/host"); - writer.WriteLine($"@{BaseUrlPropertyName} = {{hostAddress}}"); - writer.WriteLine(); - } - /// /// Retrieves the base URL for the given request builder class. /// @@ -110,7 +105,7 @@ private static void WriteBaseUrl(string? baseUrl, LanguageWriter writer) { // Retrieve the base URL property from the request builder class return requestBuilderClass.Properties - .FirstOrDefault(property => property.Name.Equals("BaseUrl", StringComparison.OrdinalIgnoreCase))?.DefaultValue; + .FirstOrDefault(property => property.Name.Equals(Constants.BaseUrl, StringComparison.OrdinalIgnoreCase))?.DefaultValue; } /// @@ -223,7 +218,7 @@ private static void WriteHttpMethods( ); // Write the HTTP operation (e.g., GET, POST, PATCH, etc.) - writer.WriteLine($"{method.Name.ToUpperInvariant()} {url} HTTP/1.1"); + writer.WriteLine($"{method.Name.ToUpperInvariant()} {url} {Constants.HttpVersion}"); var authenticationMethod = requestBuilderClass .Properties @@ -233,10 +228,10 @@ private static void WriteHttpMethods( { var schemeTypeMapping = new Dictionary { - { SecuritySchemeType.ApiKey.ToString().ToLowerInvariant(), "apiKeyAuth" }, - { SecuritySchemeType.Http.ToString().ToLowerInvariant(), "bearerAuth" }, - { SecuritySchemeType.OAuth2.ToString().ToLowerInvariant(), "bearerAuth" }, - { SecuritySchemeType.OpenIdConnect.ToString().ToLowerInvariant(), "bearerAuth" } + { SecuritySchemeType.ApiKey.ToString().ToLowerInvariant(), Constants.ApiKeyAuth }, + { SecuritySchemeType.Http.ToString().ToLowerInvariant(), Constants.BearerAuth }, + { SecuritySchemeType.OAuth2.ToString().ToLowerInvariant(), Constants.BearerAuth }, + { SecuritySchemeType.OpenIdConnect.ToString().ToLowerInvariant(), Constants.BearerAuth } }; var schemeType = schemeTypeMapping[authenticationMethod.Type.Name.ToLowerInvariant()]; writer.WriteLine($"Authorization: {{{{{schemeType}}}}}"); @@ -302,10 +297,7 @@ private static void WriteRequestBody(CodeMethod method, LanguageWriter writer) private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter writer, HashSet? processedClasses = null, int depth = 0) { - if (processedClasses == null) - { - processedClasses = new HashSet(); - } + processedClasses ??= []; // Add the current class to the set of processed classes if (!processedClasses.Add(requestBodyClass)) @@ -357,7 +349,7 @@ private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter w private static string BuildUrlStringFromTemplate(string urlTemplateString, List queryParameters, List pathParameters, string? baseUrl) { // Use the provided baseUrl or default to "http://localhost/" - baseUrl ??= "http://localhost/"; + baseUrl ??= Constants.LocalHostUrl; // unquote the urlTemplate string and replace the {+baseurl} with the actual base url string urlTemplateString = urlTemplateString.Trim('"').Replace("{+baseurl}", baseUrl, StringComparison.InvariantCultureIgnoreCase); @@ -371,6 +363,6 @@ private static string BuildUrlStringFromTemplate(string urlTemplateString, List< }; // Erase baseUrl and use the placeholder variable {baseUrl} already defined in the snippet - return requestInformation.URI.ToString().Replace(baseUrl, $"{{{{{BaseUrlPropertyName}}}}}", StringComparison.InvariantCultureIgnoreCase); + return requestInformation.URI.ToString().Replace(baseUrl, $"{{{{{Constants.BaseUrlPropertyName}}}}}", StringComparison.InvariantCultureIgnoreCase); } } From 2380019012712cc24a5648d9292302f2f7109a9b Mon Sep 17 00:00:00 2001 From: Onokaev Date: Mon, 20 Jan 2025 12:46:06 +0300 Subject: [PATCH 55/66] Fix formatting --- .../Settings/SettingsFileManagementService.cs | 4 ++-- .../Settings/VsCodeFileManagement.cs | 24 +++++++------------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index ff8eda864a..b4d3c2eef5 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -37,7 +37,7 @@ public Task WriteSettingsFileAsync(string directoryPath, OpenApiDocument openApi private static SettingsFile GenerateSettingsFile(OpenApiDocument openApiDocument) { var settings = new SettingsFile(); - if(openApiDocument.Servers?.Count > 0) + if (openApiDocument.Servers?.Count > 0) { settings.EnvironmentVariables.Development.HostAddress = openApiDocument.Servers[0].Url; settings.EnvironmentVariables.Remote.HostAddress = openApiDocument.Servers[0].Url; @@ -51,6 +51,6 @@ private async Task WriteSettingsFileInternalAsync(string directoryPath, Settings var vscodeDirectoryPath = GetDirectoryContainingSettingsFile(parentDirectoryPath!); var settingsObjectString = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.SettingsFile); var fileUpdatePath = Path.Combine(vscodeDirectoryPath, SettingsFileName); - await VsCodeSettingsManager.UpdateFileAsync(settingsObjectString, EnvironmentVariablesKey, fileUpdatePath, cancellationToken).ConfigureAwait(false); + await VsCodeSettingsManager.UpdateFileAsync(settingsObjectString, fileUpdatePath, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Kiota.Builder/Settings/VsCodeFileManagement.cs b/src/Kiota.Builder/Settings/VsCodeFileManagement.cs index f3529ffcd0..d081c7aa39 100644 --- a/src/Kiota.Builder/Settings/VsCodeFileManagement.cs +++ b/src/Kiota.Builder/Settings/VsCodeFileManagement.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Text.Json; @@ -15,7 +15,7 @@ public static class VsCodeSettingsManager WriteIndented = true, }; private static readonly SettingsFileGenerationContext context = new(options); - public static async Task UpdateFileAsync(string fileUpdate, string fileUpdateKey, string fileUpdatePath, CancellationToken cancellationToken) + public static async Task UpdateFileAsync(string fileUpdate, string fileUpdatePath, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrEmpty(fileUpdate); Dictionary settings; @@ -23,25 +23,19 @@ public static async Task UpdateFileAsync(string fileUpdate, string fileUpdateKey // Read existing settings or create new if file doesn't exist if (File.Exists(fileUpdatePath)) { - string jsonContent = await File.ReadAllTextAsync(fileUpdatePath, cancellationToken).ConfigureAwait(false); - try - { - settings = JsonSerializer.Deserialize( - jsonContent, - context.DictionaryStringObject) - ?? []; - } - catch (JsonException) - { - settings = []; - } + using var stream = File.OpenRead(fileUpdatePath); + settings = await JsonSerializer.DeserializeAsync( + stream, + context.DictionaryStringObject, + cancellationToken + ).ConfigureAwait(false) ?? []; } else { settings = []; } - var fileUpdateDictionary = JsonSerializer.Deserialize>(fileUpdate, context.DictionaryStringObject); + var fileUpdateDictionary = JsonSerializer.Deserialize(fileUpdate, context.DictionaryStringObject); if (fileUpdateDictionary is not null) { foreach (var kvp in fileUpdateDictionary) From 4a8b5b560aa1591c7da10a9ffd0dfff3a81ed15d Mon Sep 17 00:00:00 2001 From: Onokaev Date: Mon, 20 Jan 2025 13:37:39 +0300 Subject: [PATCH 56/66] Use generic writer for no-op writing operations --- src/Kiota.Builder/Writers/GenericElementWriter.cs | 13 +++++++++++++ .../Writers/HTTP/CodeBlockEndWriter.cs | 12 ------------ src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs | 15 --------------- .../Writers/HTTP/CodeMethodWriter.cs | 13 ------------- .../Writers/HTTP/CodeNamespaceWriter.cs | 14 -------------- .../Writers/HTTP/CodePropertyWriter.cs | 14 -------------- src/Kiota.Builder/Writers/HTTP/HttpWriter.cs | 3 +-- .../Writers/HTTP/CodeEnumWriterTests.cs | 4 ++-- 8 files changed, 16 insertions(+), 72 deletions(-) create mode 100644 src/Kiota.Builder/Writers/GenericElementWriter.cs delete mode 100644 src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs delete mode 100644 src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs delete mode 100644 src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs delete mode 100644 src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs delete mode 100644 src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs diff --git a/src/Kiota.Builder/Writers/GenericElementWriter.cs b/src/Kiota.Builder/Writers/GenericElementWriter.cs new file mode 100644 index 0000000000..751fb94535 --- /dev/null +++ b/src/Kiota.Builder/Writers/GenericElementWriter.cs @@ -0,0 +1,13 @@ +using System; +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers; + +public class GenericElementWriter(ILanguageConventionService conventionService) : BaseElementWriter(conventionService) +{ + public override void WriteCodeElement(CodeElement codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + } +} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs deleted file mode 100644 index c59fd59d9c..0000000000 --- a/src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Kiota.Builder.CodeDOM; - -namespace Kiota.Builder.Writers.Http; -public class CodeBlockEndWriter : ICodeElementWriter -{ - public void WriteCodeElement(BlockEnd codeElement, LanguageWriter writer) - { - ArgumentNullException.ThrowIfNull(codeElement); - ArgumentNullException.ThrowIfNull(writer); - } -} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs deleted file mode 100644 index 2c0a14d506..0000000000 --- a/src/Kiota.Builder/Writers/HTTP/CodeEnumWriter.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Linq; - -using Kiota.Builder.CodeDOM; -using Kiota.Builder.Extensions; - -namespace Kiota.Builder.Writers.Http; -public class CodeEnumWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) -{ - public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter writer) - { - ArgumentNullException.ThrowIfNull(codeElement); - ArgumentNullException.ThrowIfNull(writer); - } -} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs deleted file mode 100644 index 4098a5cbb5..0000000000 --- a/src/Kiota.Builder/Writers/HTTP/CodeMethodWriter.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Linq; -using Kiota.Builder.CodeDOM; - -namespace Kiota.Builder.Writers.Http; -public class CodeMethodWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) -{ - public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) - { - ArgumentNullException.ThrowIfNull(codeElement); - ArgumentNullException.ThrowIfNull(writer); - } -} diff --git a/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs deleted file mode 100644 index 47b679b029..0000000000 --- a/src/Kiota.Builder/Writers/HTTP/CodeNamespaceWriter.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Linq; - -using Kiota.Builder.CodeDOM; - -namespace Kiota.Builder.Writers.Http; -public class CodeNamespaceWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) -{ - public override void WriteCodeElement(CodeNamespace codeElement, LanguageWriter writer) - { - ArgumentNullException.ThrowIfNull(codeElement); - ArgumentNullException.ThrowIfNull(writer); - } -} diff --git a/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs deleted file mode 100644 index 09cfd734ba..0000000000 --- a/src/Kiota.Builder/Writers/HTTP/CodePropertyWriter.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using Kiota.Builder.CodeDOM; -using Kiota.Builder.Extensions; - -namespace Kiota.Builder.Writers.Http; -public class CodePropertyWriter(HttpConventionService conventionService) : BaseElementWriter(conventionService) -{ - public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter writer) - { - ArgumentNullException.ThrowIfNull(codeElement); - ArgumentNullException.ThrowIfNull(writer); - if (codeElement.Parent is null) throw new InvalidOperationException("The parent of a property should be a class"); - } -} diff --git a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs index e0aafbbc8a..0423e95cc6 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs @@ -9,7 +9,6 @@ public HttpWriter(string rootPath, string clientNamespaceName) PathSegmenter = new HttpPathSegmenter(rootPath, clientNamespaceName); var conventionService = new HttpConventionService(); AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); - AddOrReplaceCodeElementWriter(new CodePropertyWriter(conventionService)); - AddOrReplaceCodeElementWriter(new CodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new GenericElementWriter(conventionService)); } } diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs index 3fb970fb89..5d7f1da83c 100644 --- a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs @@ -16,11 +16,11 @@ public sealed class CodeEnumWriterTests : IDisposable private readonly LanguageWriter writer; private readonly CodeEnum currentEnum; private const string EnumName = "someEnum"; - private readonly CodeEnumWriter codeEnumWriter; + private readonly GenericElementWriter codeEnumWriter; public CodeEnumWriterTests() { writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.HTTP, DefaultPath, DefaultName); - codeEnumWriter = new CodeEnumWriter(new HttpConventionService()); + codeEnumWriter = new GenericElementWriter(new HttpConventionService()); tw = new StringWriter(); writer.SetTextWriter(tw); var root = CodeNamespace.InitRootNamespace(); From b7c87d13b21fe302396063e3b1869e5632b4d306 Mon Sep 17 00:00:00 2001 From: Onokaev Date: Mon, 20 Jan 2025 14:55:52 +0300 Subject: [PATCH 57/66] Refactor generic writers --- .../Settings/SettingsFileManagementService.cs | 2 +- .../Settings/VsCodeFileManagement.cs | 16 +++++----- .../Writers/GenericElementWriter.cs | 13 -------- .../GenericWriters/GenericElementWriter.cs | 32 +++++++++++++++++++ src/Kiota.Builder/Writers/HTTP/HttpWriter.cs | 4 ++- .../Writers/HTTP/CodeEnumWriterTests.cs | 4 +-- 6 files changed, 46 insertions(+), 25 deletions(-) delete mode 100644 src/Kiota.Builder/Writers/GenericElementWriter.cs create mode 100644 src/Kiota.Builder/Writers/GenericWriters/GenericElementWriter.cs diff --git a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs index b4d3c2eef5..c519c3538a 100644 --- a/src/Kiota.Builder/Settings/SettingsFileManagementService.cs +++ b/src/Kiota.Builder/Settings/SettingsFileManagementService.cs @@ -51,6 +51,6 @@ private async Task WriteSettingsFileInternalAsync(string directoryPath, Settings var vscodeDirectoryPath = GetDirectoryContainingSettingsFile(parentDirectoryPath!); var settingsObjectString = JsonSerializer.Serialize(settings, SettingsFileGenerationContext.Default.SettingsFile); var fileUpdatePath = Path.Combine(vscodeDirectoryPath, SettingsFileName); - await VsCodeSettingsManager.UpdateFileAsync(settingsObjectString, fileUpdatePath, cancellationToken).ConfigureAwait(false); + await VsCodeSettingsManager.UpdateFileAsync(settingsObjectString, fileUpdatePath, EnvironmentVariablesKey, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Kiota.Builder/Settings/VsCodeFileManagement.cs b/src/Kiota.Builder/Settings/VsCodeFileManagement.cs index d081c7aa39..2a77373322 100644 --- a/src/Kiota.Builder/Settings/VsCodeFileManagement.cs +++ b/src/Kiota.Builder/Settings/VsCodeFileManagement.cs @@ -15,10 +15,10 @@ public static class VsCodeSettingsManager WriteIndented = true, }; private static readonly SettingsFileGenerationContext context = new(options); - public static async Task UpdateFileAsync(string fileUpdate, string fileUpdatePath, CancellationToken cancellationToken) + public static async Task UpdateFileAsync(string fileUpdate, string fileUpdatePath, string fileUpdateKey, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrEmpty(fileUpdate); - Dictionary settings; + Dictionary settings = []; // Read existing settings or create new if file doesn't exist if (File.Exists(fileUpdatePath)) @@ -30,17 +30,17 @@ public static async Task UpdateFileAsync(string fileUpdate, string fileUpdatePat cancellationToken ).ConfigureAwait(false) ?? []; } - else - { - settings = []; - } var fileUpdateDictionary = JsonSerializer.Deserialize(fileUpdate, context.DictionaryStringObject); if (fileUpdateDictionary is not null) { - foreach (var kvp in fileUpdateDictionary) + if (fileUpdateDictionary.TryGetValue(fileUpdateKey, out var environmentVariables)) + { + settings[fileUpdateKey] = environmentVariables; + } + else { - settings[kvp.Key] = kvp.Value; + settings[fileUpdateKey] = fileUpdateDictionary; } } diff --git a/src/Kiota.Builder/Writers/GenericElementWriter.cs b/src/Kiota.Builder/Writers/GenericElementWriter.cs deleted file mode 100644 index 751fb94535..0000000000 --- a/src/Kiota.Builder/Writers/GenericElementWriter.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using Kiota.Builder.CodeDOM; - -namespace Kiota.Builder.Writers; - -public class GenericElementWriter(ILanguageConventionService conventionService) : BaseElementWriter(conventionService) -{ - public override void WriteCodeElement(CodeElement codeElement, LanguageWriter writer) - { - ArgumentNullException.ThrowIfNull(codeElement); - ArgumentNullException.ThrowIfNull(writer); - } -} diff --git a/src/Kiota.Builder/Writers/GenericWriters/GenericElementWriter.cs b/src/Kiota.Builder/Writers/GenericWriters/GenericElementWriter.cs new file mode 100644 index 0000000000..82b6831aa7 --- /dev/null +++ b/src/Kiota.Builder/Writers/GenericWriters/GenericElementWriter.cs @@ -0,0 +1,32 @@ +using System; +using Kiota.Builder.CodeDOM; + +namespace Kiota.Builder.Writers; + +// Generic base class for writing code elements +public class GenericWriter(ILanguageConventionService conventionService) + : BaseElementWriter(conventionService) + where TElement : CodeElement +{ + public override void WriteCodeElement(TElement codeElement, LanguageWriter writer) + { + ArgumentNullException.ThrowIfNull(codeElement); + ArgumentNullException.ThrowIfNull(writer); + } +} + +// Specific writers inheriting from the generic class +public class GenericCodePropertyWriter(ILanguageConventionService conventionService) + : GenericWriter(conventionService) +{ +} + +public class GenericCodeMethodWriter(ILanguageConventionService conventionService) + : GenericWriter(conventionService) +{ +} + +public class GenericCodeElementWriter(ILanguageConventionService conventionService) + : GenericWriter(conventionService) +{ +} diff --git a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs index 0423e95cc6..6e97831c74 100644 --- a/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/HttpWriter.cs @@ -9,6 +9,8 @@ public HttpWriter(string rootPath, string clientNamespaceName) PathSegmenter = new HttpPathSegmenter(rootPath, clientNamespaceName); var conventionService = new HttpConventionService(); AddOrReplaceCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); - AddOrReplaceCodeElementWriter(new GenericElementWriter(conventionService)); + AddOrReplaceCodeElementWriter(new GenericCodePropertyWriter(conventionService)); + AddOrReplaceCodeElementWriter(new GenericCodeMethodWriter(conventionService)); + AddOrReplaceCodeElementWriter(new GenericCodeElementWriter(conventionService)); } } diff --git a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs index 5d7f1da83c..a1631daa52 100644 --- a/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/HTTP/CodeEnumWriterTests.cs @@ -16,11 +16,11 @@ public sealed class CodeEnumWriterTests : IDisposable private readonly LanguageWriter writer; private readonly CodeEnum currentEnum; private const string EnumName = "someEnum"; - private readonly GenericElementWriter codeEnumWriter; + private readonly GenericCodeElementWriter codeEnumWriter; public CodeEnumWriterTests() { writer = LanguageWriter.GetLanguageWriter(GenerationLanguage.HTTP, DefaultPath, DefaultName); - codeEnumWriter = new GenericElementWriter(new HttpConventionService()); + codeEnumWriter = new GenericCodeElementWriter(new HttpConventionService()); tw = new StringWriter(); writer.SetTextWriter(tw); var root = CodeNamespace.InitRootNamespace(); From d8a0ff6edf17fcce3de3fea72b69d1808ca68837 Mon Sep 17 00:00:00 2001 From: Onokaev Date: Mon, 20 Jan 2025 15:19:37 +0300 Subject: [PATCH 58/66] Refactor constants --- src/Kiota.Builder/KiotaBuilder.cs | 2 +- src/Kiota.Builder/Refiners/HttpRefiner.cs | 6 ++++-- src/Kiota.Builder/Settings/SettingsFile.cs | 6 +++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 618b353d3e..ab5c0c3b4c 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -292,7 +292,7 @@ public async Task GenerateClientAsync(CancellationToken cancellationToken) { sw.Start(); await settingsFileManagementService.WriteSettingsFileAsync(config.OutputPath, openApiDocument, cancellationToken).ConfigureAwait(false); - StopLogAndReset(sw, $"step {++stepId} - generated settings file for HTTP authentication - took"); + StopLogAndReset(sw, $"step {++stepId} - generating settings file for HTTP authentication - took"); } return stepId; }, cancellationToken).ConfigureAwait(false); diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 4c22b02778..2bf6ddfe03 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -11,6 +11,8 @@ namespace Kiota.Builder.Refiners; public class HttpRefiner(GenerationConfiguration configuration) : CommonLanguageRefiner(configuration) { + private const string BaseUrl = "BaseUrl"; + private const string BaseUrlName = "string"; public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken cancellationToken) { return Task.Run(() => @@ -66,11 +68,11 @@ private static void SetBaseUrlForRequestBuilderMethods(CodeElement current, stri // Add a new property named BaseUrl and set its value to the baseUrl string var baseUrlProperty = new CodeProperty { - Name = "BaseUrl", + Name = BaseUrl, Kind = CodePropertyKind.Custom, Access = AccessModifier.Private, DefaultValue = baseUrl, - Type = new CodeType { Name = "string", IsExternal = true } + Type = new CodeType { Name = BaseUrlName, IsExternal = true } }; codeClass.AddProperty(baseUrlProperty); } diff --git a/src/Kiota.Builder/Settings/SettingsFile.cs b/src/Kiota.Builder/Settings/SettingsFile.cs index 1a673e01ee..7a9dbe2926 100644 --- a/src/Kiota.Builder/Settings/SettingsFile.cs +++ b/src/Kiota.Builder/Settings/SettingsFile.cs @@ -58,19 +58,19 @@ public string HostAddress } [JsonPropertyName("basicAuth")] - public string? BasicAuth + public string BasicAuth { get; set; } [JsonPropertyName("bearerAuth")] - public string? BearerAuth + public string BearerAuth { get; set; } [JsonPropertyName("apiKeyAuth")] - public string? ApiKey + public string ApiKey { get; set; } From 759a7ad25c733926e9ffd5e40571dcad68fbd633 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Mon, 20 Jan 2025 21:36:54 +0300 Subject: [PATCH 59/66] - Refactor constsnts - Use TryGet to avoid reference resolution errors --- src/Kiota.Builder/KiotaBuilder.cs | 6 ++++-- .../Settings/VsCodeFileManagement.cs | 2 ++ .../Writers/HTTP/CodeClassDeclarationWriter.cs | 17 +++++++++-------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 794235083e..061f497269 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -579,8 +579,10 @@ private void AddOperationSecurityRequirementToDOM(OpenApiOperation operation, Co { foreach (var scheme in securityRequirement.Keys) { - var securityScheme = securitySchemes[scheme.Reference.Id]; - AddSecurity(codeClass, securityScheme); + if (securitySchemes.TryGetValue(scheme.Reference.Id, out var securityScheme)) + { + AddSecurity(codeClass, securityScheme); + } } } } diff --git a/src/Kiota.Builder/Settings/VsCodeFileManagement.cs b/src/Kiota.Builder/Settings/VsCodeFileManagement.cs index 2a77373322..eef4690580 100644 --- a/src/Kiota.Builder/Settings/VsCodeFileManagement.cs +++ b/src/Kiota.Builder/Settings/VsCodeFileManagement.cs @@ -18,6 +18,8 @@ public static class VsCodeSettingsManager public static async Task UpdateFileAsync(string fileUpdate, string fileUpdatePath, string fileUpdateKey, CancellationToken cancellationToken) { ArgumentException.ThrowIfNullOrEmpty(fileUpdate); + ArgumentException.ThrowIfNullOrEmpty(fileUpdatePath); + ArgumentException.ThrowIfNullOrEmpty(fileUpdateKey); Dictionary settings = []; // Read existing settings or create new if file doesn't exist diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index bb8db13f5a..7034f48ba0 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -18,6 +18,14 @@ private static class Constants internal const string BearerAuth = "bearerAuth"; internal const string HttpVersion = "HTTP/1.1"; internal const string LocalHostUrl = "http://localhost/"; + + internal static Dictionary SchemeTypeMapping = new() + { + { SecuritySchemeType.ApiKey.ToString().ToLowerInvariant(), ApiKeyAuth }, + { SecuritySchemeType.Http.ToString().ToLowerInvariant(), BearerAuth }, + { SecuritySchemeType.OAuth2.ToString().ToLowerInvariant(), BearerAuth }, + { SecuritySchemeType.OpenIdConnect.ToString().ToLowerInvariant(), BearerAuth } + }; } protected override void WriteTypeDeclaration(ClassDeclaration codeElement, LanguageWriter writer) @@ -226,14 +234,7 @@ private static void WriteHttpMethods( if (authenticationMethod != null && Enum.TryParse(typeof(SecuritySchemeType), authenticationMethod.Type.Name, true, out var _)) { - var schemeTypeMapping = new Dictionary - { - { SecuritySchemeType.ApiKey.ToString().ToLowerInvariant(), Constants.ApiKeyAuth }, - { SecuritySchemeType.Http.ToString().ToLowerInvariant(), Constants.BearerAuth }, - { SecuritySchemeType.OAuth2.ToString().ToLowerInvariant(), Constants.BearerAuth }, - { SecuritySchemeType.OpenIdConnect.ToString().ToLowerInvariant(), Constants.BearerAuth } - }; - var schemeType = schemeTypeMapping[authenticationMethod.Type.Name.ToLowerInvariant()]; + var schemeType = Constants.SchemeTypeMapping[authenticationMethod.Type.Name.ToLowerInvariant()]; writer.WriteLine($"Authorization: {{{{{schemeType}}}}}"); } From d446cb90699e2197b5593efb372f7567ec31619e Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Mon, 20 Jan 2025 21:47:53 +0300 Subject: [PATCH 60/66] Pass ISettingsManagementService as a constructor param --- src/Kiota.Builder/KiotaBuilder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 061f497269..8025360676 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -47,10 +47,10 @@ public partial class KiotaBuilder private readonly ParallelOptions parallelOptions; private readonly HttpClient httpClient; private OpenApiDocument? openApiDocument; - private readonly SettingsFileManagementService settingsFileManagementService = new(); + private readonly ISettingsManagementService settingsFileManagementService; internal void SetOpenApiDocument(OpenApiDocument document) => openApiDocument = document ?? throw new ArgumentNullException(nameof(document)); - public KiotaBuilder(ILogger logger, GenerationConfiguration config, HttpClient client, bool useKiotaConfig = false) + public KiotaBuilder(ILogger logger, GenerationConfiguration config, HttpClient client, bool useKiotaConfig = false, ISettingsManagementService? settingsManagementService = null) { ArgumentNullException.ThrowIfNull(logger); ArgumentNullException.ThrowIfNull(config); @@ -66,6 +66,7 @@ public KiotaBuilder(ILogger logger, GenerationConfiguration config workspaceManagementService = new WorkspaceManagementService(logger, client, useKiotaConfig, workingDirectory); this.useKiotaConfig = useKiotaConfig; openApiDocumentDownloadService = new OpenApiDocumentDownloadService(client, logger); + settingsFileManagementService = settingsManagementService ?? new SettingsFileManagementService(); } private readonly OpenApiDocumentDownloadService openApiDocumentDownloadService; private readonly bool useKiotaConfig; From 55ae3c880d59fa82fa7cc17cd54a564bc143ca05 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Mon, 20 Jan 2025 22:41:52 +0300 Subject: [PATCH 61/66] Fix formatting --- .../Writers/HTTP/CodeClassDeclarationWriter.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 7034f48ba0..ed286336a2 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -232,10 +232,12 @@ private static void WriteHttpMethods( .Properties .FirstOrDefault(static prop => prop.IsOfKind(CodePropertyKind.Headers)); - if (authenticationMethod != null && Enum.TryParse(typeof(SecuritySchemeType), authenticationMethod.Type.Name, true, out var _)) + if (authenticationMethod != null + && Enum.TryParse(typeof(SecuritySchemeType), authenticationMethod.Type.Name, true, out var schemeTypeObj) + && schemeTypeObj is SecuritySchemeType schemeType + && Constants.SchemeTypeMapping.TryGetValue(schemeType.ToString().ToLowerInvariant(), out var mappedSchemeType)) { - var schemeType = Constants.SchemeTypeMapping[authenticationMethod.Type.Name.ToLowerInvariant()]; - writer.WriteLine($"Authorization: {{{{{schemeType}}}}}"); + writer.WriteLine($"Authorization: {{{{{mappedSchemeType}}}}}"); } // Write the request body if present From f453306b50c26e3d7ea4398352274edb3e626814 Mon Sep 17 00:00:00 2001 From: "Evans Aboge (from Dev Box)" Date: Tue, 21 Jan 2025 10:49:08 +0300 Subject: [PATCH 62/66] Use arrays to improve memory efficiency --- .../HTTP/CodeClassDeclarationWriter.cs | 84 +++++++++++-------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index ed286336a2..6f77ecd717 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -62,27 +62,34 @@ protected override void WriteTypeDeclaration(ClassDeclaration codeElement, Langu /// Retrieves all the query parameters for the given request builder class. /// /// The request builder class containing the query parameters. - /// A list of all query parameters. - private static List GetAllQueryParameters(CodeClass requestBuilderClass) + /// An array of all query parameters. + private static CodeProperty[] GetAllQueryParameters(CodeClass requestBuilderClass) { - var queryParameters = new List(); - // Retrieve all the query parameter classes var queryParameterClasses = requestBuilderClass .GetChildElements(true) .OfType() .Where(static element => element.IsOfKind(CodeClassKind.QueryParameters)) - .ToList(); + .ToArray(); - // Collect all query parameter properties into the aggregated list - queryParameterClasses.ForEach(paramCodeClass => - { - var queryParams = paramCodeClass - .Properties - .SelectMany(static property => property.IsOfKind(CodePropertyKind.QueryParameter) ? [property] : Array.Empty()); + // Calculate the total number of query parameters + int totalQueryParameters = queryParameterClasses + .Sum(paramCodeClass => paramCodeClass.Properties + .Count(static property => property.IsOfKind(CodePropertyKind.QueryParameter))); + + // Create an array to hold all query parameters + var queryParameters = new CodeProperty[totalQueryParameters]; + int index = 0; - queryParameters.AddRange(queryParams); - }); + // Populate the array with query parameters + foreach (var paramCodeClass in queryParameterClasses) + { + foreach (var property in paramCodeClass.Properties + .Where(static property => property.IsOfKind(CodePropertyKind.QueryParameter))) + { + queryParameters[index++] = property; + } + } return queryParameters; } @@ -91,15 +98,15 @@ private static List GetAllQueryParameters(CodeClass requestBuilder /// Retrieves all the path parameters for the given request builder class. /// /// The request builder class containing the path parameters. - /// A list of all path parameters, or an empty list if none are found. - private static List GetPathParameters(CodeClass requestBuilderClass) + /// An array of all path parameters, or an empty array if none are found. + private static CodeProperty[] GetPathParameters(CodeClass requestBuilderClass) { // Retrieve all the path variables except the generic path parameter named "pathParameters" var pathParameters = requestBuilderClass .GetChildElements(true) .OfType() .Where(property => property.IsOfKind(CodePropertyKind.PathParameters) && !property.Name.Equals(Constants.PathParameters, StringComparison.OrdinalIgnoreCase)) - .ToList(); + .ToArray(); return pathParameters; } @@ -150,23 +157,33 @@ private static void WriteUrlTemplate(CodeProperty urlTemplateProperty, LanguageW /// /// Writes the path parameters for the given request builder class to the writer. /// - /// The list of path parameters to write. + /// The array of path parameters to write. /// The language writer to write the path parameters to. - private static void WritePathParameters(List pathParameters, LanguageWriter writer) + private static void WritePathParameters(CodeProperty[] pathParameters, LanguageWriter writer) { + if (pathParameters is null) return; + // Write each path parameter property - pathParameters?.ForEach(prop => WriteHttpParameterProperty(prop, writer)); + foreach (var pathParameter in pathParameters) + { + WriteHttpParameterProperty(pathParameter, writer); + } } /// /// Writes the query parameters for the given request builder class to the writer. /// - /// The list of query parameters to write. + /// The array of query parameters to write. /// The language writer to write the query parameters to. - private static void WriteQueryParameters(List queryParameters, LanguageWriter writer) + private static void WriteQueryParameters(CodeProperty[] queryParameters, LanguageWriter writer) { + if (queryParameters is null) return; + // Write each query parameter property - queryParameters.ForEach(prop => WriteHttpParameterProperty(prop, writer)); + foreach (var queryParameter in queryParameters) + { + WriteHttpParameterProperty(queryParameter, writer); + } } /// @@ -194,22 +211,22 @@ private static void WriteHttpParameterProperty(CodeProperty property, LanguageWr /// /// The request builder class containing the HTTP methods. /// The language writer to write the HTTP methods to. - /// The list of query parameters. - /// The list of path parameters. + /// The array of query parameters. + /// The array of path parameters. /// The URL template property containing the URL template. /// The base URL. private static void WriteHttpMethods( CodeClass requestBuilderClass, LanguageWriter writer, - List queryParameters, - List pathParameters, + CodeProperty[] queryParameters, + CodeProperty[] pathParameters, CodeProperty urlTemplateProperty, string? baseUrl) { // Retrieve all the HTTP methods of kind RequestExecutor var httpMethods = GetHttpMethods(requestBuilderClass); - var methodCount = httpMethods.Count; + var methodCount = httpMethods.Length; var currentIndex = 0; foreach (var method in httpMethods) @@ -232,7 +249,7 @@ private static void WriteHttpMethods( .Properties .FirstOrDefault(static prop => prop.IsOfKind(CodePropertyKind.Headers)); - if (authenticationMethod != null + if (authenticationMethod != null && Enum.TryParse(typeof(SecuritySchemeType), authenticationMethod.Type.Name, true, out var schemeTypeObj) && schemeTypeObj is SecuritySchemeType schemeType && Constants.SchemeTypeMapping.TryGetValue(schemeType.ToString().ToLowerInvariant(), out var mappedSchemeType)) @@ -257,14 +274,13 @@ private static void WriteHttpMethods( /// Retrieves all the HTTP methods of kind RequestExecutor for the given request builder class. /// /// The request builder class containing the HTTP methods. - /// A list of HTTP methods of kind RequestExecutor. - private static List GetHttpMethods(CodeClass requestBuilderClass) + /// An array of HTTP methods of kind RequestExecutor. + private static CodeMethod[] GetHttpMethods(CodeClass requestBuilderClass) { - return requestBuilderClass + return [.. requestBuilderClass .GetChildElements(true) .OfType() - .Where(static element => element.IsOfKind(CodeMethodKind.RequestExecutor)) - .ToList(); + .Where(static element => element.IsOfKind(CodeMethodKind.RequestExecutor))]; } /// @@ -349,7 +365,7 @@ private static void WriteProperties(CodeClass requestBodyClass, LanguageWriter w } } - private static string BuildUrlStringFromTemplate(string urlTemplateString, List queryParameters, List pathParameters, string? baseUrl) + private static string BuildUrlStringFromTemplate(string urlTemplateString, CodeProperty[] queryParameters, CodeProperty[] pathParameters, string? baseUrl) { // Use the provided baseUrl or default to "http://localhost/" baseUrl ??= Constants.LocalHostUrl; From 0e2dae77b72c5f224bea33076cb08928cbac8d2f Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 21 Jan 2025 07:19:49 -0500 Subject: [PATCH 63/66] chore: applies trivial suggestions --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 2 +- src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 2bf6ddfe03..775b9cf81f 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -106,7 +106,7 @@ private void AddPathParameters(CodeElement element) // Retrieve all the parameters of kind CodeParameterKind.Custom var customParameters = codeIndexer.Parameters .Where(static param => param.IsOfKind(CodeParameterKind.Custom)) - .ToList(); + .ToArray(); // For each parameter: foreach (var param in customParameters) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 6f77ecd717..8c3adf0a5c 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -161,8 +161,6 @@ private static void WriteUrlTemplate(CodeProperty urlTemplateProperty, LanguageW /// The language writer to write the path parameters to. private static void WritePathParameters(CodeProperty[] pathParameters, LanguageWriter writer) { - if (pathParameters is null) return; - // Write each path parameter property foreach (var pathParameter in pathParameters) { @@ -177,8 +175,6 @@ private static void WritePathParameters(CodeProperty[] pathParameters, LanguageW /// The language writer to write the query parameters to. private static void WriteQueryParameters(CodeProperty[] queryParameters, LanguageWriter writer) { - if (queryParameters is null) return; - // Write each query parameter property foreach (var queryParameter in queryParameters) { From fcd5cfc707d585bb4f92efd049b2532e5ad2f2fd Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 21 Jan 2025 07:21:45 -0500 Subject: [PATCH 64/66] fix: reduces complexity of query parameters properties listing --- .../HTTP/CodeClassDeclarationWriter.cs | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs index 8c3adf0a5c..ed52fb5cb2 100644 --- a/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs +++ b/src/Kiota.Builder/Writers/HTTP/CodeClassDeclarationWriter.cs @@ -66,32 +66,13 @@ protected override void WriteTypeDeclaration(ClassDeclaration codeElement, Langu private static CodeProperty[] GetAllQueryParameters(CodeClass requestBuilderClass) { // Retrieve all the query parameter classes - var queryParameterClasses = requestBuilderClass + return requestBuilderClass .GetChildElements(true) .OfType() .Where(static element => element.IsOfKind(CodeClassKind.QueryParameters)) + .SelectMany(paramCodeClass => paramCodeClass.Properties) + .Where(static property => property.IsOfKind(CodePropertyKind.QueryParameter)) .ToArray(); - - // Calculate the total number of query parameters - int totalQueryParameters = queryParameterClasses - .Sum(paramCodeClass => paramCodeClass.Properties - .Count(static property => property.IsOfKind(CodePropertyKind.QueryParameter))); - - // Create an array to hold all query parameters - var queryParameters = new CodeProperty[totalQueryParameters]; - int index = 0; - - // Populate the array with query parameters - foreach (var paramCodeClass in queryParameterClasses) - { - foreach (var property in paramCodeClass.Properties - .Where(static property => property.IsOfKind(CodePropertyKind.QueryParameter))) - { - queryParameters[index++] = property; - } - } - - return queryParameters; } /// From a6edc30872cf10d85cc277fc5e441bae194bf6c2 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 21 Jan 2025 07:26:19 -0500 Subject: [PATCH 65/66] chore: refactor custom parameters handling in HttpRefiner --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 34 ++++++++++------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 775b9cf81f..137cbf8d1c 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -101,30 +101,26 @@ private void AddPathParameters(CodeElement element) .OfType() .FirstOrDefault(static x => x.IsOfKind(CodeMethodKind.IndexerBackwardCompatibility)); - if (codeIndexer is not null) + if (codeIndexer is not null && element is CodeClass codeClass) { // Retrieve all the parameters of kind CodeParameterKind.Custom - var customParameters = codeIndexer.Parameters - .Where(static param => param.IsOfKind(CodeParameterKind.Custom)) - .ToArray(); - - // For each parameter: - foreach (var param in customParameters) - { - // Create a new property of kind CodePropertyKind.PathParameters using the parameter and add it to the codeClass - var pathParameterProperty = new CodeProperty + var customProperties = codeIndexer.Parameters + .Where(static x => x.IsOfKind(CodeParameterKind.Custom)) + .Select(x => new CodeProperty { - Name = param.Name, + Name = x.Name, Kind = CodePropertyKind.PathParameters, - Type = param.Type, + Type = x.Type, Access = AccessModifier.Public, - DefaultValue = param.DefaultValue, - SerializationName = param.SerializationName, - Documentation = param.Documentation - }; - - if (element is CodeClass codeClass) - codeClass.AddProperty(pathParameterProperty); + DefaultValue = x.DefaultValue, + SerializationName = x.SerializationName, + Documentation = x.Documentation + }) + .ToArray(); + + if (customProperties.Length > 0) + { + codeClass.AddProperty(customProperties); } } From 326ad1aef4adcb456c610421d4d8599e7f6238b4 Mon Sep 17 00:00:00 2001 From: Vincent Biret Date: Tue, 21 Jan 2025 07:41:10 -0500 Subject: [PATCH 66/66] chore: formatting --- src/Kiota.Builder/Refiners/HttpRefiner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Kiota.Builder/Refiners/HttpRefiner.cs b/src/Kiota.Builder/Refiners/HttpRefiner.cs index 137cbf8d1c..e023cade8d 100644 --- a/src/Kiota.Builder/Refiners/HttpRefiner.cs +++ b/src/Kiota.Builder/Refiners/HttpRefiner.cs @@ -117,7 +117,7 @@ private void AddPathParameters(CodeElement element) Documentation = x.Documentation }) .ToArray(); - + if (customProperties.Length > 0) { codeClass.AddProperty(customProperties);