From 8dd143d5863dd3143092f18cda462f0c720b47e0 Mon Sep 17 00:00:00 2001 From: koros Date: Wed, 9 Oct 2024 16:10:46 +0300 Subject: [PATCH 01/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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;