Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/http language support #5778

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
3 changes: 2 additions & 1 deletion src/Kiota.Builder/GenerationLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ public enum GenerationLanguage
Go,
Swift,
Ruby,
CLI
CLI,
HTTP
}
13 changes: 13 additions & 0 deletions src/Kiota.Builder/PathSegmenters/HttpPathSegmenter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
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)
{
return GetLastFileNameSegment(currentElement).ToFirstCharacterUpperCase();
}
}
154 changes: 154 additions & 0 deletions src/Kiota.Builder/Refiners/HttpRefiner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
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(GenerationConfiguration configuration) : CommonLanguageRefiner(configuration)
{
public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken cancellationToken)
{
return Task.Run(() =>
{
cancellationToken.ThrowIfCancellationRequested();
CapitalizeNamespacesFirstLetters(generatedCode);
ReplaceIndexersByMethodsWithParameter(
generatedCode,
false,
static x => $"By{x.ToFirstCharacterUpperCase()}",
static x => x.ToFirstCharacterUpperCase(),
GenerationLanguage.HTTP);
cancellationToken.ThrowIfCancellationRequested();
ReplaceReservedNames(
generatedCode,
new HttpReservedNamesProvider(),
x => $"{x}_escaped");
RemoveCancellationParameter(generatedCode);
ConvertUnionTypesToWrapper(
generatedCode,
_configuration.UsesBackingStore,
static s => s
);
cancellationToken.ThrowIfCancellationRequested();
SetBaseUrlForRequestBuilderMethods(generatedCode, GetBaseUrl(generatedCode));
// Remove unused code from the DOM e.g Models, BarrelInitializers, e.t.c
RemoveUnusedCodeElements(generatedCode);
}, cancellationToken);
}

private string? GetBaseUrl(CodeElement element)
{
return element.GetImmediateParentOfType<CodeNamespace>()
.GetRootNamespace()?
.FindChildrenByName<CodeClass>(_configuration.ClientClassName)?
.FirstOrDefault()?
.Methods?
.FirstOrDefault(x => x.IsOfKind(CodeMethodKind.ClientConstructor))?
.BaseUrl;
}

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 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 static void RemoveUnusedCodeElements(CodeElement element)
{
if (!IsRequestBuilderClass(element) || IsBaseRequestBuilder(element) || IsRequestBuilderClassWithoutAnyHttpOperations(element))
{
var parentNameSpace = element.GetImmediateParentOfType<CodeNamespace>();
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<CodeNamespace>().Parent;
while (parent is not null)
{
var codeIndexer = parent.GetChildElements(false)
.OfType<CodeClass>()
.FirstOrDefault()?
.GetChildElements(false)
.OfType<CodeMethod>()
.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<CodeNamespace>();
}
}

private static bool IsRequestBuilderClass(CodeElement element)
{
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));
}
}
12 changes: 12 additions & 0 deletions src/Kiota.Builder/Refiners/HttpReservedNamesProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;

namespace Kiota.Builder.Refiners;
public class HttpReservedNamesProvider : IReservedNamesProvider
{
private readonly Lazy<HashSet<string>> _reservedNames = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
"any"
// TODO (HTTP) add full list
});
public HashSet<string> ReservedNames => _reservedNames.Value;
}
3 changes: 3 additions & 0 deletions src/Kiota.Builder/Refiners/ILanguageRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/Kiota.Builder/Writers/HTTP/CodeBlockEndWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using Kiota.Builder.CodeDOM;

namespace Kiota.Builder.Writers.http;
public class CodeBlockEndWriter : ICodeElementWriter<BlockEnd>
{
public void WriteCodeElement(BlockEnd codeElement, LanguageWriter writer)
{
ArgumentNullException.ThrowIfNull(codeElement);
ArgumentNullException.ThrowIfNull(writer);
}
}
Loading
Loading