Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions src/Kiota.Builder/Export/PublicAPIExportService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Kiota.Builder.Writers.Php;
using Kiota.Builder.Writers.Python;
using Kiota.Builder.Writers.Ruby;
using Kiota.Builder.Writers.Rust;
using Kiota.Builder.Writers.TypeScript;

namespace Kiota.Builder.Export;
Expand Down Expand Up @@ -123,6 +124,7 @@ private static ILanguageConventionService GetLanguageConventionServiceFromConfig
GenerationLanguage.Go => new GoConventionService(),
GenerationLanguage.Ruby => new RubyConventionService(),
GenerationLanguage.Dart => new DartConventionService(),
GenerationLanguage.Rust => new RustConventionService(),
_ => throw new ArgumentOutOfRangeException(nameof(generationConfiguration), generationConfiguration.Language, null)
};
}
Expand Down
1 change: 1 addition & 0 deletions src/Kiota.Builder/GenerationLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ public enum GenerationLanguage
Go,
Ruby,
Dart,
Rust,
HTTP
}
13 changes: 13 additions & 0 deletions src/Kiota.Builder/PathSegmenters/RustPathSegmenter.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 RustPathSegmenter(string rootPath, string clientNamespaceName) : CommonPathSegmenter(rootPath, clientNamespaceName)
{
public override string FileSuffix => ".rs";

public override string NormalizeNamespaceSegment(string segmentName) => segmentName.ToSnakeCase();

public override string NormalizeFileName(CodeElement currentElement) => GetLastFileNameSegment(currentElement).ToSnakeCase();
}
3 changes: 3 additions & 0 deletions src/Kiota.Builder/Refiners/ILanguageRefiner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public static async Task RefineAsync(GenerationConfiguration config, CodeNamespa
case GenerationLanguage.Dart:
await new DartRefiner(config).RefineAsync(generatedCode, cancellationToken).ConfigureAwait(false);
break;
case GenerationLanguage.Rust:
await new RustRefiner(config).RefineAsync(generatedCode, cancellationToken).ConfigureAwait(false);
break;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;

namespace Kiota.Builder.Refiners;

public class RustExceptionsReservedNamesProvider : IReservedNamesProvider
{
private readonly Lazy<HashSet<string>> _reservedNames = new(static () => new(StringComparer.Ordinal)
{
"to_string",
"fmt",
"source",
});
public HashSet<string> ReservedNames => _reservedNames.Value;
}
368 changes: 368 additions & 0 deletions src/Kiota.Builder/Refiners/RustRefiner.cs

Large diffs are not rendered by default.

84 changes: 84 additions & 0 deletions src/Kiota.Builder/Refiners/RustReservedNamesProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;

namespace Kiota.Builder.Refiners;

public class RustReservedNamesProvider : IReservedNamesProvider
{
private readonly Lazy<HashSet<string>> _reservedNames = new(static () => new(StringComparer.Ordinal) {
// Strict keywords
"as",
"async",
"await",
"break",
"const",
"continue",
"crate",
"dyn",
"else",
"enum",
"extern",
"false",
"fn",
"for",
"if",
"impl",
"in",
"let",
"loop",
"match",
"mod",
"move",
"mut",
"pub",
"ref",
"return",
"self",
"Self",
"static",
"struct",
"super",
"trait",
"true",
"type",
"unsafe",
"use",
"where",
"while",
// Reserved for future use
"abstract",
"become",
"box",
"do",
"final",
"macro",
"override",
"priv",
"try",
"typeof",
"unsized",
"virtual",
"yield",
// Weak keywords used in certain contexts
"union",
"dyn",
// Common standard library types/traits that could collide
"String",
"Vec",
"Box",
"Option",
"Result",
"HashMap",
"Clone",
"Default",
"Display",
"Debug",
"Iterator",
"From",
"Into",
"Error",
// Kiota base type names
"BaseRequestBuilder",
});
public HashSet<string> ReservedNames => _reservedNames.Value;
}
2 changes: 2 additions & 0 deletions src/Kiota.Builder/Writers/LanguageWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Kiota.Builder.Writers.Php;
using Kiota.Builder.Writers.Python;
using Kiota.Builder.Writers.Ruby;
using Kiota.Builder.Writers.Rust;
using Kiota.Builder.Writers.TypeScript;

namespace Kiota.Builder.Writers;
Expand Down Expand Up @@ -190,6 +191,7 @@ public static LanguageWriter GetLanguageWriter(GenerationLanguage language, stri
GenerationLanguage.Python => new PythonWriter(outputPath, clientNamespaceName, usesBackingStore),
GenerationLanguage.Go => new GoWriter(outputPath, clientNamespaceName, excludeBackwardCompatible),
GenerationLanguage.Dart => new DartWriter(outputPath, clientNamespaceName),
GenerationLanguage.Rust => new RustWriter(outputPath, clientNamespaceName),
GenerationLanguage.HTTP => new HttpWriter(outputPath, clientNamespaceName),
_ => throw new InvalidEnumArgumentException($"{language} language currently not supported."),
};
Expand Down
13 changes: 13 additions & 0 deletions src/Kiota.Builder/Writers/Rust/CodeBlockEndWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using Kiota.Builder.CodeDOM;

namespace Kiota.Builder.Writers.Rust;

public class CodeBlockEndWriter : ICodeElementWriter<BlockEnd>
{
public void WriteCodeElement(BlockEnd codeElement, LanguageWriter writer)
{
ArgumentNullException.ThrowIfNull(writer);
writer.CloseBlock();
}
}
122 changes: 122 additions & 0 deletions src/Kiota.Builder/Writers/Rust/CodeClassDeclarationWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
using System;
using System.Linq;
using Kiota.Builder.CodeDOM;
using Kiota.Builder.Extensions;
using Kiota.Builder.PathSegmenters;

namespace Kiota.Builder.Writers.Rust;

public class CodeClassDeclarationWriter : BaseElementWriter<ClassDeclaration, RustConventionService>
{
private readonly RelativeImportManager relativeImportManager;
private readonly string clientNamespaceName;

public CodeClassDeclarationWriter(RustConventionService conventionService, string clientNamespaceName, RustPathSegmenter pathSegmenter) : base(conventionService)
{
ArgumentNullException.ThrowIfNull(pathSegmenter);
this.clientNamespaceName = clientNamespaceName;
relativeImportManager = new RelativeImportManager(clientNamespaceName, '.', (ns, element) => pathSegmenter.NormalizeFileName(element));
}

public override void WriteCodeElement(ClassDeclaration codeElement, LanguageWriter writer)
{
ArgumentNullException.ThrowIfNull(codeElement);
ArgumentNullException.ThrowIfNull(writer);

if (codeElement.Parent is not CodeClass parentClass)
throw new InvalidOperationException($"The provided code element {codeElement.Name} doesn't have a parent of type {nameof(CodeClass)}");

conventions.WriteAutogeneratedMessage(writer);
writer.WriteLine();

// Write use statements for external dependencies
if (codeElement.Parent?.Parent is CodeNamespace)
{
// Standard imports for generated Rust files
writer.WriteLine("use std::collections::HashMap;");

foreach (var externalUsing in codeElement.Usings
.Where(static x => x.IsExternal)
.DistinctBy(static x => x.Declaration!.Name, StringComparer.Ordinal)
.OrderBy(static x => x.Declaration!.Name, StringComparer.Ordinal))
{
var declName = externalUsing.Declaration!.Name;
if (declName.Equals("kiota_abstractions", StringComparison.Ordinal))
writer.WriteLine("use kiota_abstractions::*;");
else if (declName.Equals("serde", StringComparison.Ordinal))
writer.WriteLine("use serde::{Serialize, Deserialize};");
else if (declName.Equals("serde_json", StringComparison.Ordinal))
writer.WriteLine("use serde_json;");
else if (declName.StartsWith("kiota_serialization_", StringComparison.Ordinal))
writer.WriteLine($"use {declName}::*;");
else
writer.WriteLine($"use {declName};");
}

foreach (var internalUsing in codeElement.Usings
.Where(static x => !x.IsExternal && x.Declaration?.TypeDefinition != null)
.DistinctBy(static x => x.Declaration?.TypeDefinition?.Name, StringComparer.OrdinalIgnoreCase)
.OrderBy(static x => x.Declaration?.TypeDefinition?.Name, StringComparer.Ordinal))
{
var typeName = internalUsing.Declaration?.TypeDefinition?.Name?.ToFirstCharacterUpperCase();
if (!string.IsNullOrEmpty(typeName))
{
var moduleFileName = typeName.ToSnakeCase();
// Build the full module path from the namespace hierarchy
var typeNamespace = internalUsing.Declaration?.TypeDefinition?.Parent as CodeNamespace;
var currentNamespace = codeElement.Parent?.Parent as CodeNamespace;
if (typeNamespace != null && currentNamespace != null)
{
var nsName = typeNamespace.Name;
var rootName = clientNamespaceName;
var relativePath = nsName.StartsWith(rootName + ".", StringComparison.Ordinal)
? nsName[(rootName.Length + 1)..]
: (nsName.Equals(rootName, StringComparison.Ordinal) ? string.Empty : nsName);
if (string.IsNullOrEmpty(relativePath))
{
writer.WriteLine($"use crate::{moduleFileName}::{typeName};");
}
else
{
var moduleParts = relativePath.Split('.').Select(p => p.ToSnakeCase());
var modulePath = string.Join("::", moduleParts);
writer.WriteLine($"use crate::{modulePath}::{moduleFileName}::{typeName};");
}
}
}
}

writer.WriteLine();
}

conventions.WriteLongDescription(parentClass, writer);
conventions.WriteDeprecationAttribute(parentClass, writer);

var isModel = parentClass.IsOfKind(CodeClassKind.Model);
var isRequestBuilder = parentClass.IsOfKind(CodeClassKind.RequestBuilder);
var isQueryParameters = parentClass.IsOfKind(CodeClassKind.QueryParameters);

if (isModel)
{
// Model classes get serde derives
var derives = new[] { "Debug", "Clone", "Default", "Serialize", "Deserialize" };
writer.WriteLine($"#[derive({string.Join(", ", derives)})]");
}
else if (isQueryParameters)
{
// Query parameter classes need Default and Clone
writer.WriteLine("#[derive(Debug, Clone, Default)]");
}
else if (isRequestBuilder)
{
// Request builders have Box<dyn> fields that can't derive Clone/Debug
}
else
{
writer.WriteLine("#[derive(Debug, Clone)]");
}

var structName = codeElement.Name.ToFirstCharacterUpperCase();
writer.StartBlock($"pub struct {structName} {{");
}
}
40 changes: 40 additions & 0 deletions src/Kiota.Builder/Writers/Rust/CodeEnumWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Linq;

using Kiota.Builder.CodeDOM;
using Kiota.Builder.Extensions;

namespace Kiota.Builder.Writers.Rust;

public class CodeEnumWriter : BaseElementWriter<CodeEnum, RustConventionService>
{
public CodeEnumWriter(RustConventionService conventionService) : base(conventionService) { }

public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter writer)
{
ArgumentNullException.ThrowIfNull(codeElement);
ArgumentNullException.ThrowIfNull(writer);
if (!codeElement.Options.Any())
return;

conventions.WriteAutogeneratedMessage(writer);
writer.WriteLine("use serde::{Serialize, Deserialize};");
writer.WriteLine();

conventions.WriteShortDescription(codeElement, writer);
conventions.WriteDeprecationAttribute(codeElement, writer);
writer.WriteLine("#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]");
writer.StartBlock($"pub enum {codeElement.Name} {{");

foreach (var option in codeElement.Options)
{
conventions.WriteShortDescription(option, writer);
var serializationName = option.SerializationName;
if (!string.IsNullOrEmpty(serializationName) && !serializationName.Equals(option.Name, StringComparison.Ordinal))
{
writer.WriteLine($"#[serde(rename = \"{serializationName}\")]");
}
writer.WriteLine($"{option.Name.ToFirstCharacterUpperCase()},");
}
}
}
Loading
Loading