diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml
index c9ebf81771..1425ca2472 100644
--- a/.azure-pipelines/ci-build.yml
+++ b/.azure-pipelines/ci-build.yml
@@ -55,6 +55,11 @@ parameters:
os: "linux"
image: "ubuntu-latest"
pool: Azure-Pipelines-1ESPT-ExDShared
+ - architecture: "linux-arm64"
+ jobPrefix: "linux_arm64"
+ os: "linux"
+ image: "ubuntu-latest"
+ pool: Azure-Pipelines-1ESPT-ExDShared
- architecture: "osx-x64"
jobPrefix: "osx_x64"
os: "macOS"
@@ -575,6 +580,7 @@ extends:
win_x64_build_binaries,
win_x86_build_binaries,
linux_x64_build_binaries,
+ linux_arm64_build_binaries,
osx_x64_build_binaries,
osx_arm64_build_binaries,
]
diff --git a/.github/workflows/idempotency-tests.yml b/.github/workflows/idempotency-tests.yml
index cf479de6d4..074325f488 100644
--- a/.github/workflows/idempotency-tests.yml
+++ b/.github/workflows/idempotency-tests.yml
@@ -63,6 +63,7 @@ jobs:
- ruby
- php
- python
+ - dart
description:
- "./tests/Kiota.Builder.IntegrationTests/InheritingErrors.yaml"
- "./tests/Kiota.Builder.IntegrationTests/NoUnderscoresInModel.yaml"
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index 487b069e4e..7e0ef9ffff 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -43,6 +43,7 @@ jobs:
- ruby
- php
- python
+ - dart
description:
- "./tests/Kiota.Builder.IntegrationTests/InheritingErrors.yaml"
- "./tests/Kiota.Builder.IntegrationTests/EnumHandling.yaml"
@@ -109,6 +110,11 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: "3.11"
+ - name: Setup Dart
+ if: matrix.language == 'dart'
+ uses: dart-lang/setup-dart@v1
+ with:
+ sdk: "stable"
- name: Check if test is suppressed
id: check-suppressed
diff --git a/.vscode/launch.json b/.vscode/launch.json
index f4bcd561b3..2638c90765 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -170,6 +170,27 @@
"console": "internalConsole",
"stopAtEntry": false
},
+ {
+ "name": "Launch Dart",
+ "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",
+ "dart",
+ "-o",
+ "${workspaceFolder}/samples/msgraph-mail/java/utilities/src/main/java/graphjavav4/utilities",
+ "-n",
+ "graphdart4.utilities"
+ ],
+ "cwd": "${workspaceFolder}/src/kiota",
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
{
"name": "Launch CLI (CSharp)",
"type": "coreclr",
@@ -385,4 +406,4 @@
"processId": "${command:pickProcess}"
}
]
-}
\ No newline at end of file
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a4befd77e..32b1b2a4c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+## [1.22.0] - 2025-01-09
+
+### Added
+
+- Adds code generation for the `Dart` programming language.Credit to @ricardoboss, @joanne-ter-maat, @Kees-Schotanus, @andreaTP, @baywet[#3745](https://github.com/microsoft/kiota/issues/3745)
+- Added new linux arm64 binary builds for Kiota CLI
+
+### Changed
+
- Fixed a bug in generation when a referenced schema in an allOf was a primitive [#5701](https://github.com/microsoft/kiota/issues/5701).
- Fixed a bug where inherited error models would be missing interface declarations. [#5888](https://github.com/microsoft/kiota/issues/5888)
- Fixed a bug where oneOf/anyOf schemas with single references to inheritance or intersections would be missing properties. [#5921](https://github.com/microsoft/kiota/issues/5921)
@@ -1521,3 +1530,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Initial GitHub release
+
diff --git a/it/Readme.md b/it/Readme.md
index aac690c34d..262e5b88c0 100644
--- a/it/Readme.md
+++ b/it/Readme.md
@@ -17,5 +17,5 @@ Generate the code:
And finally run the test:
```bash
-./it/exec-cmd.ps1 -language ${LANG}
+./it/exec-cmd.ps1 -descriptionUrl ${FILE/URL} -language ${LANG}
```
diff --git a/it/dart/.gitignore b/it/dart/.gitignore
new file mode 100644
index 0000000000..4f66d2275a
--- /dev/null
+++ b/it/dart/.gitignore
@@ -0,0 +1,3 @@
+pubspec.lock
+.dart_tool/
+src/
\ No newline at end of file
diff --git a/it/dart/basic/test/api_client_test.dart b/it/dart/basic/test/api_client_test.dart
new file mode 100644
index 0000000000..6bc18fd191
--- /dev/null
+++ b/it/dart/basic/test/api_client_test.dart
@@ -0,0 +1,24 @@
+import 'package:microsoft_kiota_abstractions/microsoft_kiota_abstractions.dart';
+import 'package:microsoft_kiota_http/microsoft_kiota_http.dart';
+import 'package:test/test.dart';
+import '../lib/api_client.dart';
+import '../lib/models/error.dart';
+
+void main() {
+ group('apiclient', () {
+ test('basic endpoint test', () {
+ final requestAdapter = HttpClientRequestAdapter(
+ client: KiotaClientFactory.createClient(),
+ authProvider: AnonymousAuthenticationProvider(),
+ pNodeFactory: ParseNodeFactoryRegistry.defaultInstance,
+ sWriterFactory: SerializationWriterFactoryRegistry.defaultInstance,
+ );
+ requestAdapter.baseUrl = "http://localhost:1080";
+ var client = ApiClient(requestAdapter);
+ expect(
+ () => client.api.v1.topics.getAsync(),
+ throwsA(predicate(
+ (e) => e is Error && e.id == 'my-sample-id' && e.code == 123)));
+ });
+ });
+}
diff --git a/it/dart/pubspec.yaml b/it/dart/pubspec.yaml
new file mode 100644
index 0000000000..8becd341e7
--- /dev/null
+++ b/it/dart/pubspec.yaml
@@ -0,0 +1,22 @@
+name: kiota_dart_generate
+description: api generation
+version: 0.0.1
+publish_to: none
+
+environment:
+ sdk: ^3.6.0
+
+# Add regular dependencies here.
+dependencies:
+ microsoft_kiota_abstractions: ^0.0.1
+ microsoft_kiota_http: ^0.0.1
+ microsoft_kiota_serialization_form: ^0.0.1
+ microsoft_kiota_serialization_text: ^0.0.1
+ microsoft_kiota_serialization_json: ^0.0.1
+ microsoft_kiota_serialization_multipart: ^0.0.1
+ http: ^1.2.2
+ uuid: ^4.5.1
+
+dev_dependencies:
+ lints: ^5.1.1
+ test: ^1.25.14
diff --git a/it/exec-cmd.ps1 b/it/exec-cmd.ps1
index ee6faf9440..0bd7595493 100755
--- a/it/exec-cmd.ps1
+++ b/it/exec-cmd.ps1
@@ -202,6 +202,29 @@ elseif ($language -eq "python") {
Pop-Location
}
}
+elseif ($language -eq "dart") {
+ Invoke-Call -ScriptBlock {
+ dart pub get
+ dart analyze lib/
+ } -ErrorAction Stop
+
+ if ($mockServerTest) {
+ Push-Location $itTestPath
+
+ $itTestPathSources = Join-Path -Path $testPath -ChildPath "lib"
+ $itTestPathDest = Join-Path -Path $itTestPath -ChildPath "lib"
+ if (Test-Path $itTestPathDest) {
+ Remove-Item $itTestPathDest -Force -Recurse
+ }
+ Copy-Item -Path $itTestPathSources -Destination $itTestPathDest -Recurse
+
+ Invoke-Call -ScriptBlock {
+ dart test
+ } -ErrorAction Stop
+
+ Pop-Location
+ }
+}
Pop-Location
if (!([string]::IsNullOrEmpty($mockSeverITFolder))) {
diff --git a/it/get-additional-arguments.ps1 b/it/get-additional-arguments.ps1
index aa1e6db286..e96e3c4a0c 100755
--- a/it/get-additional-arguments.ps1
+++ b/it/get-additional-arguments.ps1
@@ -23,6 +23,9 @@ if ($language -eq "csharp") {
elseif ($language -eq "java") {
$command = " --output `"./it/$language/src/apisdk`""
}
+elseif ($language -eq "dart") {
+ $command = " --output `"./it/$language/lib`""
+}
elseif ($language -eq "go") {
$command = " --output `"./it/$language/client`" --namespace-name `"integrationtest/client`""
}
diff --git a/src/Kiota.Builder/GenerationLanguage.cs b/src/Kiota.Builder/GenerationLanguage.cs
index dbd236ade7..278bdcd8b2 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,
+ Dart,
}
diff --git a/src/Kiota.Builder/Kiota.Builder.csproj b/src/Kiota.Builder/Kiota.Builder.csproj
index 63eb4d6f87..fa2971d235 100644
--- a/src/Kiota.Builder/Kiota.Builder.csproj
+++ b/src/Kiota.Builder/Kiota.Builder.csproj
@@ -15,7 +15,7 @@
Microsoft.OpenApi.Kiota.Builder
Microsoft.OpenApi.Kiota.Builder
./nupkg
- 1.22.0
+ 1.23.0
$(VersionSuffix)
https://github.com/microsoft/kiota/releases
diff --git a/src/Kiota.Builder/PathSegmenters/DartPathSegmenter.cs b/src/Kiota.Builder/PathSegmenters/DartPathSegmenter.cs
new file mode 100644
index 0000000000..4869be49e6
--- /dev/null
+++ b/src/Kiota.Builder/PathSegmenters/DartPathSegmenter.cs
@@ -0,0 +1,20 @@
+using System;
+using Kiota.Builder.CodeDOM;
+using Kiota.Builder.Extensions;
+using Kiota.Builder.Writers.Go;
+
+namespace Kiota.Builder.PathSegmenters;
+
+public class DartPathSegmenter(string rootPath, string clientNamespaceName) : CommonPathSegmenter(rootPath, clientNamespaceName)
+{
+ public override string FileSuffix => ".dart";
+
+ public override string NormalizeNamespaceSegment(string segmentName) => segmentName.ToCamelCase();
+
+ public override string NormalizeFileName(CodeElement currentElement) => GetLastFileNameSegment(currentElement).ToSnakeCase();
+
+ internal string GetRelativeFileName(CodeNamespace @namespace, CodeElement element)
+ {
+ return NormalizeFileName(element);
+ }
+}
diff --git a/src/Kiota.Builder/Refiners/DartExceptionsReservedNamesProvider.cs b/src/Kiota.Builder/Refiners/DartExceptionsReservedNamesProvider.cs
new file mode 100644
index 0000000000..892d157958
--- /dev/null
+++ b/src/Kiota.Builder/Refiners/DartExceptionsReservedNamesProvider.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+
+namespace Kiota.Builder.Refiners;
+public class DartExceptionsReservedNamesProvider : IReservedNamesProvider
+{
+ private readonly Lazy> _reservedNames = new(static () => new(StringComparer.OrdinalIgnoreCase)
+ {
+ "toString"
+ });
+ public HashSet ReservedNames => _reservedNames.Value;
+}
diff --git a/src/Kiota.Builder/Refiners/DartRefiner.cs b/src/Kiota.Builder/Refiners/DartRefiner.cs
new file mode 100644
index 0000000000..054415c1ad
--- /dev/null
+++ b/src/Kiota.Builder/Refiners/DartRefiner.cs
@@ -0,0 +1,520 @@
+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;
+using Kiota.Builder.Writers.Dart;
+
+
+namespace Kiota.Builder.Refiners;
+public class DartRefiner : CommonLanguageRefiner, ILanguageRefiner
+{
+ private const string MultipartBodyClassName = "MultipartBody";
+ private const string AbstractionsNamespaceName = "microsoft_kiota_abstractions/microsoft_kiota_abstractions";
+ private const string SerializationNamespaceName = "microsoft_kiota_serialization";
+ private static readonly CodeUsingDeclarationNameComparer usingComparer = new();
+
+ protected static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = {
+ new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter),
+ AbstractionsNamespaceName, "RequestAdapter"),
+ new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator),
+ AbstractionsNamespaceName, "Method", "RequestInformation", "RequestOption"),
+ new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Serializer),
+ AbstractionsNamespaceName, "SerializationWriter"),
+ new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.Deserializer),
+ AbstractionsNamespaceName, "ParseNode"),
+ new (static x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model),
+ AbstractionsNamespaceName, "Parsable"),
+ new (static x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model) && @class.Properties.Any(x => x.IsOfKind(CodePropertyKind.AdditionalData)),
+ AbstractionsNamespaceName, "AdditionalDataHolder"),
+ new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor),
+ AbstractionsNamespaceName, "Parsable"),
+ new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.QueryParameter) && !string.IsNullOrEmpty(prop.SerializationName),
+ AbstractionsNamespaceName, "QueryParameterAttribute"),
+ new (static x => x is CodeClass @class && @class.OriginalComposedType is CodeIntersectionType intersectionType && intersectionType.Types.Any(static y => !y.IsExternal),
+ AbstractionsNamespaceName, "ParseNodeHelper"),
+ new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.Headers),
+ AbstractionsNamespaceName, "RequestHeaders"),
+ new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.Custom) && prop.Type.Name.Equals(KiotaBuilder.UntypedNodeName, StringComparison.OrdinalIgnoreCase),
+ AbstractionsNamespaceName, KiotaBuilder.UntypedNodeName),
+ new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator) && method.Parameters.Any(static y => y.IsOfKind(CodeParameterKind.RequestBody) && y.Type.Name.Equals(MultipartBodyClassName, StringComparison.OrdinalIgnoreCase)),
+ AbstractionsNamespaceName, MultipartBodyClassName),
+ };
+
+
+ public DartRefiner(GenerationConfiguration configuration) : base(configuration) { }
+ public override Task RefineAsync(CodeNamespace generatedCode, CancellationToken cancellationToken)
+ {
+ return Task.Run(() =>
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var defaultConfiguration = new GenerationConfiguration();
+
+ ConvertUnionTypesToWrapper(generatedCode,
+ _configuration.UsesBackingStore,
+ static s => s.ToFirstCharacterLowerCase(),
+ false);
+ ReplaceIndexersByMethodsWithParameter(generatedCode,
+ false,
+ static x => $"by{x.ToPascalCase('_')}",
+ static x => x.ToCamelCase('_'),
+ GenerationLanguage.Dart);
+ CorrectCommonNames(generatedCode);
+ var reservedNamesProvider = new DartReservedNamesProvider();
+ cancellationToken.ThrowIfCancellationRequested();
+ CorrectNames(generatedCode, s =>
+ {
+ if (s.Contains('_', StringComparison.OrdinalIgnoreCase) &&
+ s.ToPascalCase(UnderscoreArray) is string refinedName &&
+ !reservedNamesProvider.ReservedNames.Contains(s) &&
+ !reservedNamesProvider.ReservedNames.Contains(refinedName))
+ return refinedName;
+ else
+ return s;
+ });
+ CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType, CorrectImplements);
+ ReplacePropertyNames(generatedCode,
+ [
+ CodePropertyKind.Custom,
+ CodePropertyKind.AdditionalData,
+ CodePropertyKind.QueryParameter,
+ CodePropertyKind.RequestBuilder,
+ ],
+ static s => s.ToCamelCase(UnderscoreArray));
+
+ AddQueryParameterExtractorMethod(generatedCode);
+ // This adds the BaseRequestBuilder class as a superclass
+ MoveRequestBuilderPropertiesToBaseType(generatedCode,
+ new CodeUsing
+ {
+ Name = "BaseRequestBuilder",
+ Declaration = new CodeType
+ {
+ Name = AbstractionsNamespaceName,
+ IsExternal = true,
+ }
+ }, addCurrentTypeAsGenericTypeParameter: true);
+ RemoveRequestConfigurationClasses(generatedCode,
+ new CodeUsing
+ {
+ Name = "RequestConfiguration",
+ Declaration = new CodeType
+ {
+ Name = AbstractionsNamespaceName,
+ IsExternal = true
+ }
+ }, new CodeType
+ {
+ Name = "DefaultQueryParameters",
+ IsExternal = true,
+ });
+ MoveQueryParameterClass(generatedCode);
+ AddDefaultImports(generatedCode, defaultUsingEvaluators);
+ AddPropertiesAndMethodTypesImports(generatedCode, true, true, true, codeTypeFilter);
+ AddParsableImplementsForModelClasses(generatedCode, "Parsable");
+ AddConstructorsForDefaultValues(generatedCode, true);
+ AddConstructorForErrorClass(generatedCode);
+ cancellationToken.ThrowIfCancellationRequested();
+ AddAsyncSuffix(generatedCode);
+ AddDiscriminatorMappingsUsingsToParentClasses(generatedCode, "ParseNode", addUsings: true, includeParentNamespace: true);
+
+ ReplaceReservedNames(generatedCode, reservedNamesProvider, x => $"{x}_");
+ ReplaceReservedModelTypes(generatedCode, reservedNamesProvider, x => $"{x}Object");
+ ReplaceReservedExceptionPropertyNames(
+ generatedCode,
+ new DartExceptionsReservedNamesProvider(),
+ static x => $"{x.ToFirstCharacterLowerCase()}_"
+ );
+
+ ReplaceDefaultSerializationModules(
+ generatedCode,
+ defaultConfiguration.Serializers,
+ new(StringComparer.OrdinalIgnoreCase) {
+ $"{SerializationNamespaceName}_json/{SerializationNamespaceName}_json.JsonSerializationWriterFactory",
+ $"{SerializationNamespaceName}_text/{SerializationNamespaceName}_text.TextSerializationWriterFactory",
+ $"{SerializationNamespaceName}_form/{SerializationNamespaceName}_form.FormSerializationWriterFactory",
+ $"{SerializationNamespaceName}_multipart/{SerializationNamespaceName}_multipart.MultipartSerializationWriterFactory",
+ }
+ );
+ ReplaceDefaultDeserializationModules(
+ generatedCode,
+ defaultConfiguration.Deserializers,
+ new(StringComparer.OrdinalIgnoreCase) {
+ $"{SerializationNamespaceName}_json/{SerializationNamespaceName}_json.JsonParseNodeFactory",
+ $"{SerializationNamespaceName}_form/{SerializationNamespaceName}_form.FormParseNodeFactory",
+ $"{SerializationNamespaceName}_text/{SerializationNamespaceName}_text.TextParseNodeFactory"
+ }
+ );
+ AddSerializationModulesImport(generatedCode,
+ [$"{AbstractionsNamespaceName}.ApiClientBuilder",
+ $"{AbstractionsNamespaceName}.SerializationWriterFactoryRegistry"],
+ [$"{AbstractionsNamespaceName}.ParseNodeFactoryRegistry"]);
+ cancellationToken.ThrowIfCancellationRequested();
+
+ AddParentClassToErrorClasses(
+ generatedCode,
+ "ApiException",
+ AbstractionsNamespaceName
+ );
+ DeduplicateErrorMappings(generatedCode);
+ RemoveCancellationParameter(generatedCode);
+ DisambiguatePropertiesWithClassNames(generatedCode);
+ RemoveMethodByKind(generatedCode, CodeMethodKind.RawUrlBuilder);
+ AddCustomMethods(generatedCode);
+ EscapeStringValues(generatedCode);
+ AliasUsingWithSameSymbol(generatedCode);
+ }, cancellationToken);
+ }
+
+ ///error classes should always have a constructor for the copyWith method
+ private void AddConstructorForErrorClass(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass codeClass && codeClass.IsErrorDefinition && !codeClass.Methods.Where(static x => x.IsOfKind(CodeMethodKind.Constructor)).Any())
+ {
+ codeClass.AddMethod(new CodeMethod
+ {
+ Name = "constructor",
+ Kind = CodeMethodKind.Constructor,
+ IsAsync = false,
+ IsStatic = false,
+ Documentation = new(new() {
+ {"TypeName", new CodeType {
+ IsExternal = false,
+ TypeDefinition = codeClass,
+ }
+ }
+ })
+ {
+ DescriptionTemplate = "Instantiates a new {TypeName} and sets the default values.",
+ },
+ Access = AccessModifier.Public,
+ ReturnType = new CodeType { Name = "void", IsExternal = true },
+ Parent = codeClass,
+ });
+ }
+ CrawlTree(currentElement, element => AddConstructorForErrorClass(element));
+ }
+
+ ///
+ /// Corrects common names so they can be used with Dart.
+ /// This normally comes down to changing the first character to lower case.
+ /// GetFieldDeserializers
is corrected to getFieldDeserializers
+ ///
+ private static void CorrectCommonNames(CodeElement currentElement)
+ {
+ if (currentElement is CodeMethod m &&
+ currentElement.Parent is CodeClass parentClass)
+ {
+ parentClass.RenameChildElement(m.Name, m.Name.ToFirstCharacterLowerCase());
+ parentClass.Name = parentClass.Name.ToFirstCharacterUpperCase();
+ }
+ else if (currentElement is CodeIndexer i)
+ {
+ i.IndexParameter.Name = i.IndexParameter.Name.ToFirstCharacterLowerCase();
+ }
+ else if (currentElement is CodeEnum e)
+ {
+ var options = e.Options.ToList();
+ foreach (var option in options)
+ {
+ option.Name = DartConventionService.getCorrectedEnumName(option.Name);
+ option.SerializationName = option.SerializationName.Replace("'", "\\'", StringComparison.OrdinalIgnoreCase);
+ }
+ ///ensure enum options with the same corrected name get a unique name
+ var nameGroups = options.Select((Option, index) => new { Option, index }).GroupBy(s => s.Option.Name).ToList();
+ foreach (var group in nameGroups.Where(g => g.Count() > 1))
+ {
+ foreach (var entry in group.Skip(1).Select((g, i) => new { g, i }))
+ {
+ options[entry.g.index].Name = options[entry.g.index].Name + entry.i;
+ }
+ }
+ }
+ else if (currentElement is CodeProperty p && p.Type is CodeType propertyType && propertyType.TypeDefinition is CodeEnum && !string.IsNullOrEmpty(p.DefaultValue))
+ {
+ p.DefaultValue = DartConventionService.getCorrectedEnumName(p.DefaultValue.Trim('"').CleanupSymbolName());
+ if (new DartReservedNamesProvider().ReservedNames.Contains(p.DefaultValue))
+ {
+ p.DefaultValue += "_";
+ }
+ }
+ CrawlTree(currentElement, element => CorrectCommonNames(element));
+ }
+
+ private static void CorrectMethodType(CodeMethod currentMethod)
+ {
+ if (currentMethod.IsOfKind(CodeMethodKind.Serializer))
+ currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.Serializer)).ToList().ForEach(x =>
+ {
+ x.Optional = false;
+ x.Type.IsNullable = true;
+ if (x.Type.Name.StartsWith('I'))
+ x.Type.Name = x.Type.Name[1..];
+ });
+ else if (currentMethod.IsOfKind(CodeMethodKind.Deserializer))
+ {
+ currentMethod.ReturnType.Name = "Map";
+ currentMethod.Name = "getFieldDeserializers";
+ }
+ else if (currentMethod.IsOfKind(CodeMethodKind.RawUrlConstructor, CodeMethodKind.ClientConstructor))
+ {
+ currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.RequestAdapter, CodeParameterKind.BackingStore))
+ .Where(x => x.Type.Name.StartsWith('I'))
+ .ToList()
+ .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I"
+ }
+ CorrectCoreTypes(currentMethod.Parent as CodeClass, DateTypesReplacements, currentMethod.Parameters
+ .Select(static x => x.Type)
+ .Union(new[] { currentMethod.ReturnType })
+ .ToArray());
+ currentMethod.Parameters.ToList().ForEach(static x => x.Name = x.Name.ToFirstCharacterLowerCase());
+ }
+
+ private static void CorrectPropertyType(CodeProperty currentProperty)
+ {
+ ArgumentNullException.ThrowIfNull(currentProperty);
+
+ if (currentProperty.IsOfKind(CodePropertyKind.Options))
+ currentProperty.DefaultValue = "List()";
+ else if (currentProperty.IsOfKind(CodePropertyKind.Headers))
+ currentProperty.DefaultValue = $"{currentProperty.Type.Name.ToFirstCharacterLowerCase()}()";
+ else if (currentProperty.IsOfKind(CodePropertyKind.RequestAdapter))
+ {
+ currentProperty.Type.Name = "RequestAdapter";
+ currentProperty.Type.IsNullable = true;
+ }
+ else if (currentProperty.IsOfKind(CodePropertyKind.BackingStore))
+ {
+ currentProperty.Type.Name = currentProperty.Type.Name[1..]; // removing the "I"
+ currentProperty.Name = currentProperty.Name.ToFirstCharacterLowerCase();
+ }
+ else if (currentProperty.IsOfKind(CodePropertyKind.QueryParameter))
+ {
+ currentProperty.DefaultValue = $"{currentProperty.Type.Name.ToFirstCharacterUpperCase()}()";
+ }
+ else if (currentProperty.IsOfKind(CodePropertyKind.AdditionalData))
+ {
+ currentProperty.Type.Name = "Map";
+ currentProperty.DefaultValue = "{}";
+ currentProperty.Name = currentProperty.Name.ToFirstCharacterLowerCase();
+ }
+ else if (currentProperty.IsOfKind(CodePropertyKind.UrlTemplate))
+ {
+ currentProperty.Type.IsNullable = true;
+ }
+ else if (currentProperty.IsOfKind(CodePropertyKind.PathParameters))
+ {
+ currentProperty.Type.IsNullable = true;
+ currentProperty.Type.Name = "Map";
+ if (!string.IsNullOrEmpty(currentProperty.DefaultValue))
+ currentProperty.DefaultValue = "{}";
+ }
+ else
+ {
+ currentProperty.Name = currentProperty.Name.ToFirstCharacterLowerCase();
+ }
+ currentProperty.Type.Name = currentProperty.Type.Name.ToFirstCharacterUpperCase();
+ CorrectCoreTypes(currentProperty.Parent as CodeClass, DateTypesReplacements, currentProperty.Type);
+ }
+
+ private static void CorrectImplements(ProprietableBlockDeclaration block)
+ {
+ block.Implements.Where(x => "IAdditionalDataHolder".Equals(x.Name, StringComparison.OrdinalIgnoreCase) || "IBackedModel".Equals(x.Name, StringComparison.OrdinalIgnoreCase)).ToList().ForEach(x => x.Name = x.Name[1..]); // skipping the I
+ }
+ public static IEnumerable codeTypeFilter(IEnumerable usingsToAdd)
+ {
+ var result = usingsToAdd.OfType().Except(usingsToAdd.Where(static codeType => codeType.Parent is ClassDeclaration declaration && declaration.Parent is CodeClass codeClass && codeClass.IsErrorDefinition));
+ var genericParameterTypes = usingsToAdd.OfType().Where(
+ static codeType => codeType.Parent is CodeParameter parameter
+ && parameter.IsOfKind(CodeParameterKind.RequestConfiguration)).Select(x => x.GenericTypeParameterValues.First());
+
+ return result.Union(genericParameterTypes);
+ }
+ protected static void AddAsyncSuffix(CodeElement currentElement)
+ {
+ if (currentElement is CodeMethod currentMethod && currentMethod.IsAsync)
+ currentMethod.Name += "Async";
+ CrawlTree(currentElement, AddAsyncSuffix);
+ }
+ private void AddQueryParameterExtractorMethod(CodeElement currentElement, string methodName = "toMap")
+ {
+ if (currentElement is CodeClass currentClass &&
+ currentClass.IsOfKind(CodeClassKind.QueryParameters))
+ {
+ currentClass.StartBlock.AddImplements(new CodeType
+ {
+ IsExternal = true,
+ Name = "AbstractQueryParameters"
+ });
+ currentClass.AddMethod(new CodeMethod
+ {
+ Name = methodName,
+ Access = AccessModifier.Public,
+ ReturnType = new CodeType
+ {
+ Name = "Map",
+ IsNullable = false,
+ },
+ IsAsync = false,
+ IsStatic = false,
+ Kind = CodeMethodKind.QueryParametersMapper,
+ Documentation = new()
+ {
+ DescriptionTemplate = "Extracts the query parameters into a map for the URI template parsing.",
+ },
+ });
+ currentClass.AddUsing(new CodeUsing
+ {
+ Name = "AbstractQueryParameters",
+ Declaration = new CodeType { Name = AbstractionsNamespaceName, IsExternal = true },
+ });
+ }
+ CrawlTree(currentElement, x => AddQueryParameterExtractorMethod(x, methodName));
+ }
+
+ private void MoveQueryParameterClass(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass currentClass &&
+ currentClass.IsOfKind(CodeClassKind.RequestBuilder))
+ {
+ var parentNamespace = currentClass.GetImmediateParentOfType();
+ var nestedClasses = currentClass.InnerClasses.Where(x => x.IsOfKind(CodeClassKind.QueryParameters));
+ foreach (CodeClass nestedClass in nestedClasses)
+ {
+ parentNamespace.AddClass(nestedClass);
+ currentClass.RemoveChildElementByName(nestedClass.Name);
+ }
+ }
+ CrawlTree(currentElement, x => MoveQueryParameterClass(x));
+ }
+
+ protected static void DisambiguatePropertiesWithClassNames(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass currentClass)
+ {
+ var sameNameProperty = currentClass.Properties
+ .FirstOrDefault(x => x.Name.Equals(currentClass.Name, StringComparison.OrdinalIgnoreCase));
+ if (sameNameProperty != null)
+ {
+ currentClass.RemoveChildElement(sameNameProperty);
+ if (string.IsNullOrEmpty(sameNameProperty.SerializationName))
+ sameNameProperty.SerializationName = sameNameProperty.Name;
+ sameNameProperty.Name = $"{sameNameProperty.Name}Prop";
+ currentClass.AddProperty(sameNameProperty);
+ }
+ }
+ CrawlTree(currentElement, DisambiguatePropertiesWithClassNames);
+ }
+ private void AddCustomMethods(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass currentClass)
+ {
+ if (currentClass.IsOfKind(CodeClassKind.RequestBuilder))
+ {
+ currentClass.AddMethod(new CodeMethod
+ {
+ Name = "clone",
+ Access = AccessModifier.Public,
+ ReturnType = new CodeType
+ {
+ Name = currentClass.Name,
+ IsNullable = false,
+ },
+ IsAsync = false,
+ IsStatic = false,
+
+ Kind = CodeMethodKind.Custom,
+ Documentation = new()
+ {
+ DescriptionTemplate = "Clones the requestbuilder.",
+ },
+ });
+ }
+ if (currentClass.IsOfKind(CodeClassKind.Model) && currentClass.IsErrorDefinition)
+ {
+ currentClass.AddMethod(new CodeMethod
+ {
+ Name = "copyWith",
+ Access = AccessModifier.Public,
+ ReturnType = new CodeType
+ {
+ Name = currentClass.Name,
+ IsNullable = false,
+ },
+ IsAsync = false,
+ IsStatic = false,
+
+ Kind = CodeMethodKind.Custom,
+ Documentation = new()
+ {
+ DescriptionTemplate = "Creates a copy of the object.",
+ },
+ });
+ }
+ }
+ CrawlTree(currentElement, x => AddCustomMethods(x));
+ }
+
+ private void EscapeStringValues(CodeElement currentElement)
+ {
+ if (currentElement is CodeProperty property)
+ {
+ if (!String.IsNullOrEmpty(property.SerializationName) && property.SerializationName.Contains('$', StringComparison.Ordinal))
+ {
+ property.SerializationName = property.SerializationName.Replace("$", "\\$", StringComparison.Ordinal);
+ }
+ if (property.DefaultValue.Contains('$', StringComparison.Ordinal))
+ {
+ property.DefaultValue = property.DefaultValue.Replace("$", "\\$", StringComparison.Ordinal);
+ }
+ }
+ else if (currentElement is CodeMethod method && method.HasUrlTemplateOverride)
+ {
+ method.UrlTemplateOverride = method.UrlTemplateOverride.Replace("$", "\\$", StringComparison.Ordinal);
+ }
+ CrawlTree(currentElement, EscapeStringValues);
+ }
+
+ private static readonly Dictionary DateTypesReplacements = new(StringComparer.OrdinalIgnoreCase) {
+
+ {"TimeSpan", ("Duration", null)},
+ {"DateTimeOffset", ("DateTime", null)},
+ {"Guid", ("UuidValue", new CodeUsing {
+ Name = "UuidValue",
+ Declaration = new CodeType {
+ Name = "uuid/uuid",
+ IsExternal = true,
+ },
+ })},
+ };
+ private static void AliasUsingWithSameSymbol(CodeElement currentElement)
+ {
+ if (currentElement is CodeClass currentClass && currentClass.StartBlock != null && currentClass.StartBlock.Usings.Any(x => !x.IsExternal))
+ {
+ var duplicatedSymbolsUsings = currentClass.StartBlock.Usings
+ .Distinct(usingComparer)
+ .Where(static x => !string.IsNullOrEmpty(x.Declaration?.Name) && x.Declaration.TypeDefinition != null)
+ .GroupBy(static x => x.Declaration!.Name, StringComparer.OrdinalIgnoreCase)
+ .Where(x => x.Count() > 1)
+ .SelectMany(x => x)
+ .Union(currentClass.StartBlock
+ .Usings
+ .Where(x => !x.IsExternal)
+ .Where(x => x.Declaration!
+ .Name
+ .Equals(currentClass.Name, StringComparison.OrdinalIgnoreCase)));
+ foreach (var usingElement in duplicatedSymbolsUsings)
+ {
+ var replacement = string.Join("_", usingElement.Declaration!.TypeDefinition!.GetImmediateParentOfType().Name
+ .Split(".", StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => x.ToLowerInvariant())
+ .ToArray());
+ usingElement.Alias = $"{(string.IsNullOrEmpty(replacement) ? string.Empty : $"{replacement}")}_{usingElement.Declaration!.TypeDefinition!.Name.ToLowerInvariant()}";
+ }
+ }
+ CrawlTree(currentElement, AliasUsingWithSameSymbol);
+ }
+}
diff --git a/src/Kiota.Builder/Refiners/DartReservedNamesProvider.cs b/src/Kiota.Builder/Refiners/DartReservedNamesProvider.cs
new file mode 100644
index 0000000000..18d1abeecf
--- /dev/null
+++ b/src/Kiota.Builder/Refiners/DartReservedNamesProvider.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+
+namespace Kiota.Builder.Refiners;
+public class DartReservedNamesProvider : IReservedNamesProvider
+{
+ private readonly Lazy> _reservedNames = new(() => new(StringComparer.OrdinalIgnoreCase) {
+ "abstract",
+ "as",
+ "assert",
+ "async",
+ "await",
+ "base",
+ "bool",
+ "break",
+ "case",
+ "catch",
+ "class",
+ "const",
+ "continue",
+ "covariant",
+ "default",
+ "deferred",
+ "do",
+ "double",
+ "dynamic",
+ "else",
+ "enum",
+ "export",
+ "extends",
+ "extension",
+ "external",
+ "factory",
+ "false",
+ "final",
+ "finally",
+ "for",
+ "Function",
+ "get",
+ "hide",
+ "if",
+ "implements",
+ "import",
+ "in",
+ "index",
+ "int",
+ "interface",
+ "is",
+ "late",
+ "library",
+ "mixin",
+ "new",
+ "null",
+ "of",
+ "on",
+ "operator",
+ "part",
+ "required",
+ "rethrow",
+ "return",
+ "sealed",
+ "set",
+ "show",
+ "static",
+ "stream",
+ "string",
+ "super",
+ "switch",
+ "sync",
+ "this",
+ "throw",
+ "true",
+ "try",
+ "type",
+ "typedef",
+ "var",
+ "void",
+ "when",
+ "with",
+ "while",
+ "yield",
+ "BaseRequestBuilder",
+ "clone"
+ });
+ public HashSet ReservedNames => _reservedNames.Value;
+}
diff --git a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs
index 2b1b4df7d8..37b42e51cb 100644
--- a/src/Kiota.Builder/Refiners/ILanguageRefiner.cs
+++ b/src/Kiota.Builder/Refiners/ILanguageRefiner.cs
@@ -40,6 +40,9 @@ public static async Task RefineAsync(GenerationConfiguration config, CodeNamespa
case GenerationLanguage.Python:
await new PythonRefiner(config).RefineAsync(generatedCode, cancellationToken).ConfigureAwait(false);
break;
+ case GenerationLanguage.Dart:
+ await new DartRefiner(config).RefineAsync(generatedCode, cancellationToken).ConfigureAwait(false);
+ break;
}
}
}
diff --git a/src/Kiota.Builder/Writers/Dart/CodeBlockEndWriter.cs b/src/Kiota.Builder/Writers/Dart/CodeBlockEndWriter.cs
new file mode 100644
index 0000000000..caf0f81346
--- /dev/null
+++ b/src/Kiota.Builder/Writers/Dart/CodeBlockEndWriter.cs
@@ -0,0 +1,12 @@
+using System;
+using Kiota.Builder.CodeDOM;
+
+namespace Kiota.Builder.Writers.Dart;
+public class CodeBlockEndWriter : ICodeElementWriter
+{
+ public void WriteCodeElement(BlockEnd codeElement, LanguageWriter writer)
+ {
+ ArgumentNullException.ThrowIfNull(writer);
+ writer.CloseBlock();
+ }
+}
diff --git a/src/Kiota.Builder/Writers/Dart/CodeClassDeclarationWriter.cs b/src/Kiota.Builder/Writers/Dart/CodeClassDeclarationWriter.cs
new file mode 100644
index 0000000000..0499688ac6
--- /dev/null
+++ b/src/Kiota.Builder/Writers/Dart/CodeClassDeclarationWriter.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Linq;
+using Kiota.Builder.CodeDOM;
+using Kiota.Builder.PathSegmenters;
+
+namespace Kiota.Builder.Writers.Dart;
+public class CodeClassDeclarationWriter : BaseElementWriter
+{
+ private readonly RelativeImportManager relativeImportManager;
+
+ public CodeClassDeclarationWriter(DartConventionService conventionService, string clientNamespaceName, DartPathSegmenter pathSegmenter) : base(conventionService)
+ {
+ ArgumentNullException.ThrowIfNull(pathSegmenter);
+ relativeImportManager = new RelativeImportManager(clientNamespaceName, '.', pathSegmenter.GetRelativeFileName);
+ }
+
+ 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.WriteLintingMessage(writer);
+ var currentNamespace = codeElement.GetImmediateParentOfType();
+
+ if (codeElement.Parent?.Parent is CodeNamespace)
+ {
+ foreach (var externalPath in codeElement.Usings
+ .Where(x => (x.Declaration?.IsExternal ?? true) || !x.Declaration.Name.Equals(codeElement.Name, StringComparison.OrdinalIgnoreCase)) // needed for circular requests patterns like message folder
+ .Where(static x => x.IsExternal)
+ .DistinctBy(static x => x.Declaration!.Name, StringComparer.Ordinal)
+ .OrderBy(static x => x.Declaration!.Name, StringComparer.Ordinal))
+ writer.WriteLine($"import 'package:{externalPath.Declaration!.Name}.dart';");
+
+ foreach (var relativePath in codeElement.Usings
+ .Where(static x => !x.IsExternal)
+ .DistinctBy(static x => $"{x.Name}{x.Declaration?.Name}", StringComparer.OrdinalIgnoreCase)
+ .Select(x => x.Declaration?.Name?.StartsWith('.') ?? false ?
+ (string.Empty, x.Alias, x.Declaration.Name) :
+ relativeImportManager.GetRelativeImportPathForUsing(x, currentNamespace))
+ .OrderBy(static x => x.Item3, StringComparer.Ordinal))
+ writer.WriteLine($"import '{relativePath.Item3}.dart'{GetAlias(relativePath.Item2)};");
+
+ writer.WriteLine();
+
+ }
+
+ var derivedTypes = (codeElement.Inherits is null ? Enumerable.Empty() : [conventions.GetTypeString(codeElement.Inherits, parentClass)]).ToArray();
+ var derivation = derivedTypes.Length != 0 ? " extends " + derivedTypes.Aggregate(static (x, y) => $"{x}, {y}") : string.Empty;
+ var implements = !codeElement.Implements.Any() ? string.Empty : $" implements {codeElement.Implements.Select(static x => x.Name).Aggregate(static (x, y) => x + ", " + y)}";
+
+ conventions.WriteAutogeneratedMessage(writer);
+ conventions.WriteLongDescription(parentClass, writer);
+ conventions.WriteDeprecationAttribute(parentClass, writer);
+ writer.StartBlock($"class {codeElement.Name}{derivation}{implements} {{");
+ }
+
+ private static String GetAlias(string alias)
+ {
+ return string.IsNullOrEmpty(alias) ? string.Empty : $" as {alias}";
+ }
+}
diff --git a/src/Kiota.Builder/Writers/Dart/CodeEnumWriter.cs b/src/Kiota.Builder/Writers/Dart/CodeEnumWriter.cs
new file mode 100644
index 0000000000..d736fb80c5
--- /dev/null
+++ b/src/Kiota.Builder/Writers/Dart/CodeEnumWriter.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Linq;
+
+using Kiota.Builder.CodeDOM;
+
+namespace Kiota.Builder.Writers.Dart;
+public class CodeEnumWriter : BaseElementWriter
+{
+ public CodeEnumWriter(DartConventionService conventionService) : base(conventionService) { }
+ public override void WriteCodeElement(CodeEnum codeElement, LanguageWriter writer)
+ {
+ ArgumentNullException.ThrowIfNull(codeElement);
+ ArgumentNullException.ThrowIfNull(writer);
+ if (!codeElement.Options.Any())
+ return;
+ var enumName = codeElement.Name;
+ conventions.WriteLintingMessage(writer);
+ conventions.WriteAutogeneratedMessage(writer);
+ conventions.WriteShortDescription(codeElement, writer);
+ conventions.WriteDeprecationAttribute(codeElement, writer);
+ writer.StartBlock($"enum {enumName} {{");
+
+ var options = codeElement.Options;
+ var lastOption = options.Last();
+
+ foreach (var option in options)
+ {
+ conventions.WriteShortDescription(option, writer);
+
+ var serializationName = option.SerializationName;
+ writer.WriteLine($"{option.Name}('{serializationName}'){(option == lastOption ? ";" : ",")}");
+ }
+ writer.WriteLine($"const {enumName}(this.value);");
+ writer.WriteLine("final String value;");
+ }
+}
diff --git a/src/Kiota.Builder/Writers/Dart/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Dart/CodeMethodWriter.cs
new file mode 100644
index 0000000000..83ff8ccbb3
--- /dev/null
+++ b/src/Kiota.Builder/Writers/Dart/CodeMethodWriter.cs
@@ -0,0 +1,865 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Kiota.Builder.CodeDOM;
+using Kiota.Builder.Extensions;
+using Kiota.Builder.OrderComparers;
+using static Kiota.Builder.CodeDOM.CodeTypeBase;
+
+namespace Kiota.Builder.Writers.Dart;
+public class CodeMethodWriter : BaseElementWriter
+{
+ public CodeMethodWriter(DartConventionService conventionService) : base(conventionService)
+ {
+
+ }
+ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer)
+ {
+ ArgumentNullException.ThrowIfNull(codeElement);
+ if (codeElement.ReturnType == null) throw new InvalidOperationException($"{nameof(codeElement.ReturnType)} should not be null");
+ ArgumentNullException.ThrowIfNull(writer);
+ if (codeElement.Parent is not CodeClass parentClass) throw new InvalidOperationException("the parent of a method should be a class");
+
+ var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement);
+ var inherits = parentClass.StartBlock.Inherits != null && !parentClass.IsErrorDefinition;
+ var isVoid = conventions.VoidTypeName.Equals(returnType, StringComparison.OrdinalIgnoreCase);
+ WriteMethodDocumentation(codeElement, writer);
+ WriteMethodPrototype(codeElement, parentClass, writer, returnType, inherits, isVoid);
+ writer.IncreaseIndent();
+
+ HandleMethodKind(codeElement, writer, inherits, parentClass, isVoid);
+ var isConstructor = codeElement.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor, CodeMethodKind.RawUrlConstructor);
+
+ if (HasEmptyConstructorBody(codeElement, parentClass, isConstructor))
+ {
+ writer.DecreaseIndent();
+ }
+ else
+ {
+ if (isConstructor && !inherits && parentClass.Properties.Where(static x => x.Kind is CodePropertyKind.AdditionalData).Any() && !parentClass.IsErrorDefinition && !parentClass.Properties.Where(static x => x.Kind is CodePropertyKind.BackingStore).Any())
+ {
+ writer.DecreaseIndent();
+ }
+ else if (isConstructor && parentClass.IsErrorDefinition)
+ {
+ if (parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.AdditionalData)).Any() && parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.BackingStore)).Any())
+ {
+ writer.CloseBlock("}) {additionalData = {};}");
+ }
+ else
+ {
+ writer.CloseBlock("});");
+ }
+ }
+ else
+ {
+ writer.CloseBlock();
+ }
+ }
+ }
+
+ private static bool HasEmptyConstructorBody(CodeMethod codeElement, CodeClass parentClass, bool isConstructor)
+ {
+ if (parentClass.IsOfKind(CodeClassKind.Model) && codeElement.IsOfKind(CodeMethodKind.Constructor) && !parentClass.IsErrorDefinition)
+ {
+ return parentClass.Properties.All(prop => string.IsNullOrEmpty(prop.DefaultValue));
+ }
+ var hasBody = codeElement.Parameters.Any(p => !p.IsOfKind(CodeParameterKind.RequestAdapter) && !p.IsOfKind(CodeParameterKind.PathParameters));
+ return isConstructor && parentClass.IsOfKind(CodeClassKind.RequestBuilder) && !codeElement.IsOfKind(CodeMethodKind.ClientConstructor) && (!hasBody || codeElement.IsOfKind(CodeMethodKind.RawUrlConstructor));
+ }
+
+ protected virtual void HandleMethodKind(CodeMethod codeElement, LanguageWriter writer, bool doesInherit, CodeClass parentClass, bool isVoid)
+ {
+ ArgumentNullException.ThrowIfNull(codeElement);
+ ArgumentNullException.ThrowIfNull(writer);
+ ArgumentNullException.ThrowIfNull(parentClass);
+ var returnType = conventions.GetTypeString(codeElement.ReturnType, codeElement);
+ var returnTypeWithoutCollectionInformation = conventions.GetTypeString(codeElement.ReturnType, codeElement, false);
+ var requestBodyParam = codeElement.Parameters.OfKind(CodeParameterKind.RequestBody);
+ var requestConfig = codeElement.Parameters.OfKind(CodeParameterKind.RequestConfiguration);
+ var requestContentType = codeElement.Parameters.OfKind(CodeParameterKind.RequestBodyContentType);
+ var requestParams = new RequestParams(requestBodyParam, requestConfig, requestContentType);
+
+ switch (codeElement.Kind)
+ {
+ case CodeMethodKind.Serializer:
+ WriteSerializerBody(doesInherit, codeElement, parentClass, writer);
+ break;
+ case CodeMethodKind.RequestGenerator:
+ WriteRequestGeneratorBody(codeElement, requestParams, parentClass, writer);
+ break;
+ case CodeMethodKind.RequestExecutor:
+ WriteRequestExecutorBody(codeElement, requestParams, parentClass, isVoid, returnTypeWithoutCollectionInformation, writer);
+ break;
+ case CodeMethodKind.Deserializer:
+ WriteDeserializerBody(doesInherit, codeElement, parentClass, writer);
+ break;
+ case CodeMethodKind.ClientConstructor:
+ WriteConstructorBody(parentClass, codeElement, writer);
+ WriteApiConstructorBody(parentClass, codeElement, writer);
+ break;
+ case CodeMethodKind.RawUrlBuilder:
+ WriteRawUrlBuilderBody(parentClass, codeElement, writer);
+ break;
+ case CodeMethodKind.Constructor:
+ case CodeMethodKind.RawUrlConstructor:
+ WriteConstructorBody(parentClass, codeElement, writer);
+ break;
+ case CodeMethodKind.IndexerBackwardCompatibility:
+ case CodeMethodKind.RequestBuilderWithParameters:
+ WriteRequestBuilderBody(parentClass, codeElement, writer);
+ break;
+ case CodeMethodKind.QueryParametersMapper:
+ WriteQueryparametersBody(parentClass, writer);
+ break;
+ case CodeMethodKind.Getter:
+ case CodeMethodKind.Setter:
+ throw new InvalidOperationException("getters and setters are automatically added on fields in Dart");
+ case CodeMethodKind.RequestBuilderBackwardCompatibility:
+ throw new InvalidOperationException("RequestBuilderBackwardCompatibility is not supported as the request builders are implemented by properties.");
+ case CodeMethodKind.ErrorMessageOverride:
+ throw new InvalidOperationException("ErrorMessageOverride is not supported as the error message is implemented by a property.");
+ case CodeMethodKind.CommandBuilder:
+ throw new InvalidOperationException("CommandBuilder methods are not implemented in this SDK. They're currently only supported in the shell language.");
+ case CodeMethodKind.Factory:
+ WriteFactoryMethodBody(codeElement, parentClass, writer);
+ break;
+ case CodeMethodKind.Custom:
+ WriteCustomMethodBody(codeElement, parentClass, writer);
+ break;
+ case CodeMethodKind.ComposedTypeMarker:
+ throw new InvalidOperationException("ComposedTypeMarker is not required as interface is explicitly implemented.");
+ default:
+ writer.WriteLine("return null;");
+ break;
+ }
+ }
+ private void WriteRawUrlBuilderBody(CodeClass parentClass, CodeMethod codeElement, LanguageWriter writer)
+ {
+ var rawUrlParameter = codeElement.Parameters.OfKind(CodeParameterKind.RawUrl) ?? throw new InvalidOperationException("RawUrlBuilder method should have a RawUrl parameter");
+ var requestAdapterProperty = parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) ?? throw new InvalidOperationException("RawUrlBuilder method should have a RequestAdapter property");
+ writer.WriteLine($"return {parentClass.Name}.withUrl({rawUrlParameter.Name}, {requestAdapterProperty.Name});");
+ }
+ private static readonly CodePropertyTypeComparer CodePropertyTypeForwardComparer = new();
+ private static readonly CodePropertyTypeComparer CodePropertyTypeBackwardComparer = new(true);
+ private void WriteFactoryMethodBodyForInheritedModel(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ writer.StartBlock($"return switch({DiscriminatorMappingVarName}) {{");
+ foreach (var mappedType in parentClass.DiscriminatorInformation.DiscriminatorMappings)
+ {
+ writer.WriteLine($"'{mappedType.Key}' => {conventions.GetTypeString(mappedType.Value.AllTypes.First(), codeElement)}(),");
+ }
+ writer.WriteLine($"_ => {parentClass.Name}(),");
+ writer.CloseBlock("};");
+ }
+ private const string ResultVarName = "result";
+ private void WriteFactoryMethodBodyForUnionModel(CodeMethod codeElement, CodeClass parentClass, CodeParameter parseNodeParameter, LanguageWriter writer)
+ {
+ writer.WriteLine($"var {ResultVarName} = {parentClass.Name}();");
+
+ if (parentClass.GetPropertiesOfKind(CodePropertyKind.Custom).Where(static x => x.Type is CodeType cType && cType.TypeDefinition is CodeClass && !cType.IsCollection).Any())
+ {
+ var discriminatorPropertyName = parentClass.DiscriminatorInformation.DiscriminatorPropertyName;
+ discriminatorPropertyName = discriminatorPropertyName.StartsWith('$') ? "\\" + discriminatorPropertyName : discriminatorPropertyName;
+ writer.WriteLine($"var {DiscriminatorMappingVarName} = {parseNodeParameter.Name}.getChildNode('{discriminatorPropertyName}')?.getStringValue();");
+ }
+ var includeElse = false;
+ foreach (var property in parentClass.GetPropertiesOfKind(CodePropertyKind.Custom)
+ .OrderBy(static x => x, CodePropertyTypeForwardComparer)
+ .ThenBy(static x => x.Name, StringComparer.Ordinal))
+ {
+ if (property.Type is CodeType propertyType)
+ if (propertyType.TypeDefinition is CodeClass && !propertyType.IsCollection)
+ {
+ var mappedType = parentClass.DiscriminatorInformation.DiscriminatorMappings.FirstOrDefault(x => x.Value.Name.Equals(propertyType.Name, StringComparison.OrdinalIgnoreCase));
+ writer.StartBlock($"{(includeElse ? "else " : string.Empty)}if('{mappedType.Key}' == {DiscriminatorMappingVarName}) {{");
+ writer.WriteLine($"{ResultVarName}.{property.Name} = {conventions.GetTypeString(propertyType, codeElement)}();");
+ writer.CloseBlock();
+ }
+ else if (propertyType.TypeDefinition is CodeClass && propertyType.IsCollection || propertyType.TypeDefinition is null || propertyType.TypeDefinition is CodeEnum)
+ {
+ var typeName = conventions.GetTypeString(propertyType, codeElement, true, false);
+ var check = propertyType.IsCollection ? ".isNotEmpty" : $" is {typeName}";
+ writer.StartBlock($"{(includeElse ? "else " : string.Empty)}if({parseNodeParameter.Name}.{GetDeserializationMethodName(propertyType, codeElement)}{check}) {{");
+ writer.WriteLine($"{ResultVarName}.{property.Name} = {parseNodeParameter.Name}.{GetDeserializationMethodName(propertyType, codeElement)};");
+ writer.CloseBlock();
+ }
+ if (!includeElse)
+ includeElse = true;
+ }
+ writer.WriteLine($"return {ResultVarName};");
+ }
+ private void WriteFactoryMethodBodyForIntersectionModel(CodeMethod codeElement, CodeClass parentClass, CodeParameter parseNodeParameter, LanguageWriter writer)
+ {
+ writer.WriteLine($"var {ResultVarName} = {parentClass.Name}();");
+ var includeElse = false;
+ foreach (var property in parentClass.GetPropertiesOfKind(CodePropertyKind.Custom)
+ .Where(static x => x.Type is not CodeType propertyType || propertyType.IsCollection || propertyType.TypeDefinition is not CodeClass)
+ .OrderBy(static x => x, CodePropertyTypeBackwardComparer)
+ .ThenBy(static x => x.Name, StringComparer.Ordinal))
+ {
+ if (property.Type is CodeType propertyType)
+ {
+ var check = propertyType.IsCollection ? ".isNotEmpty" : " != null";
+ writer.StartBlock($"{(includeElse ? "else " : string.Empty)}if({parseNodeParameter.Name}.{GetDeserializationMethodName(propertyType, codeElement)}{check}) {{");
+ writer.WriteLine($"{ResultVarName}.{property.Name} = {parseNodeParameter.Name}.{GetDeserializationMethodName(propertyType, codeElement)};");
+ writer.CloseBlock();
+ }
+ if (!includeElse)
+ includeElse = true;
+ }
+ var complexProperties = parentClass.GetPropertiesOfKind(CodePropertyKind.Custom)
+ .Where(static x => x.Type is CodeType xType && xType.TypeDefinition is CodeClass && !xType.IsCollection)
+ .Select(static x => new Tuple(x, (CodeType)x.Type))
+ .ToArray();
+ if (complexProperties.Length != 0)
+ {
+ if (includeElse)
+ {
+ writer.StartBlock("else {");
+ }
+ foreach (var property in complexProperties)
+ writer.WriteLine($"{ResultVarName}.{property.Item1.Name} = {conventions.GetTypeString(property.Item2, codeElement)}();");
+ if (includeElse)
+ {
+ writer.CloseBlock();
+ }
+ }
+ writer.WriteLine($"return {ResultVarName};");
+ }
+
+ private const string DiscriminatorMappingVarName = "mappingValue";
+ private void WriteFactoryMethodBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ var parseNodeParameter = codeElement.Parameters.OfKind(CodeParameterKind.ParseNode) ?? throw new InvalidOperationException("Factory method should have a ParseNode parameter");
+
+ if (parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForInheritedType)
+ {
+ var discriminatorPropertyName = parentClass.DiscriminatorInformation.DiscriminatorPropertyName;
+ discriminatorPropertyName = discriminatorPropertyName.StartsWith('$') ? "\\" + discriminatorPropertyName : discriminatorPropertyName;
+ writer.WriteLine($"var {DiscriminatorMappingVarName} = {parseNodeParameter.Name}.getChildNode('{discriminatorPropertyName}')?.getStringValue();");
+ WriteFactoryMethodBodyForInheritedModel(codeElement, parentClass, writer);
+ }
+ else if (parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForUnionType)
+ WriteFactoryMethodBodyForUnionModel(codeElement, parentClass, parseNodeParameter, writer);
+ else if (parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForIntersectionType)
+ WriteFactoryMethodBodyForIntersectionModel(codeElement, parentClass, parseNodeParameter, writer);
+ else if (parentClass.IsErrorDefinition && parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.AdditionalData)).Any() && !parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.BackingStore)).Any())
+ {
+ writer.WriteLine($"return {parentClass.Name}(additionalData: {{}});");
+ }
+ else
+ writer.WriteLine($"return {parentClass.Name}();");
+ }
+
+ private void WriteRequestBuilderBody(CodeClass parentClass, CodeMethod codeElement, LanguageWriter writer)
+ {
+ var importSymbol = conventions.GetTypeString(codeElement.ReturnType, parentClass);
+ conventions.AddRequestBuilderBody(parentClass, importSymbol, writer, prefix: "return ", pathParameters: codeElement.Parameters.Where(static x => x.IsOfKind(CodeParameterKind.Path)), customParameters: codeElement.Parameters.Where(static x => x.IsOfKind(CodeParameterKind.Custom)));
+ }
+ private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer)
+ {
+ if (parentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is not CodeProperty requestAdapterProperty) return;
+ var pathParametersProperty = parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters);
+ var backingStoreParameter = method.Parameters.OfKind(CodeParameterKind.BackingStore);
+ var requestAdapterPropertyName = requestAdapterProperty.Name;
+ WriteSerializationRegistration(method.SerializerModules, writer, "registerDefaultSerializer");
+ WriteSerializationRegistration(method.DeserializerModules, writer, "registerDefaultDeserializer");
+ if (!string.IsNullOrEmpty(method.BaseUrl))
+ {
+ writer.StartBlock($"if ({requestAdapterPropertyName}.baseUrl == null || {requestAdapterPropertyName}.baseUrl!.isEmpty) {{");
+ writer.WriteLine($"{requestAdapterPropertyName}.baseUrl = '{method.BaseUrl}';");
+ writer.CloseBlock();
+ if (pathParametersProperty != null)
+ writer.WriteLine($"{pathParametersProperty.Name}['baseurl'] = {requestAdapterPropertyName}.baseUrl;");
+ }
+ if (backingStoreParameter != null)
+ {
+ writer.StartBlock($"if ({backingStoreParameter.Name} != null) {{");
+ writer.WriteLine($"{requestAdapterPropertyName}.enableBackingStore({backingStoreParameter.Name});");
+ writer.CloseBlock();
+ }
+ }
+ private static void WriteSerializationRegistration(HashSet serializationClassNames, LanguageWriter writer, string methodName)
+ {
+ if (serializationClassNames != null)
+ foreach (var serializationClassName in serializationClassNames)
+ writer.WriteLine($"ApiClientBuilder.{methodName}({serializationClassName}.new);");
+ }
+ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMethod, LanguageWriter writer)
+ {
+ if (parentClass.IsErrorDefinition)
+ {
+ WriteErrorClassConstructor(parentClass, writer);
+ }
+ else
+ {
+ var separator = ',';
+ var propWithDefaults = parentClass.Properties
+ .Where(static x => !string.IsNullOrEmpty(x.DefaultValue) && !x.IsOfKind(CodePropertyKind.UrlTemplate, CodePropertyKind.PathParameters, CodePropertyKind.BackingStore))
+ // do not apply the default value if the type is composed as the default value may not necessarily which type to use
+ .Where(static x => x.Type is not CodeType propType || propType.TypeDefinition is not CodeClass propertyClass || propertyClass.OriginalComposedType is null)
+ .OrderByDescending(static x => x.Kind)
+ .ThenBy(static x => x.Name).ToArray();
+ var lastOption = propWithDefaults.LastOrDefault();
+
+ foreach (var propWithDefault in propWithDefaults)
+ {
+ var defaultValue = propWithDefault.DefaultValue;
+ if (propWithDefault == lastOption)
+ {
+ separator = ';';
+ }
+ if (propWithDefault.Type is CodeType propertyType && propertyType.TypeDefinition is CodeEnum)
+ {
+ defaultValue = $"{conventions.GetTypeString(propWithDefault.Type, currentMethod).TrimEnd('?')}.{defaultValue}";
+ }
+ else if (propWithDefault.Type is CodeType propertyType2)
+ {
+ defaultValue = defaultValue.Trim('"');
+ if (propertyType2.Name.Equals("String", StringComparison.Ordinal))
+ {
+ defaultValue = $"'{defaultValue}'";
+ }
+ }
+ writer.WriteLine($"{propWithDefault.Name} = {defaultValue}{separator}");
+ }
+ if (parentClass.IsOfKind(CodeClassKind.RequestBuilder) &&
+ parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProp &&
+ currentMethod.IsOfKind(CodeMethodKind.Constructor) &&
+ currentMethod.Parameters.OfKind(CodeParameterKind.PathParameters) is CodeParameter pathParametersParam)
+ {
+ var pathParameters = currentMethod.Parameters.Where(static x => x.IsOfKind(CodeParameterKind.Path));
+ if (pathParameters.Any())
+ conventions.AddParametersAssignment(writer,
+ pathParametersParam.Type,
+ pathParametersParam.Name,
+ pathParametersProp.Name,
+ currentMethod.Parameters
+ .Where(static x => x.IsOfKind(CodeParameterKind.Path))
+ .Select(static x => (x.Type, string.IsNullOrEmpty(x.SerializationName) ? x.Name : x.SerializationName, x.Name))
+ .ToArray());
+ }
+ }
+ }
+
+ private void WriteErrorClassConstructor(CodeClass parentClass, LanguageWriter writer)
+ {
+ foreach (string prop in DartConventionService.ErrorClassProperties)
+ {
+ writer.WriteLine($"super.{prop},");
+ }
+ if (!parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.BackingStore)).Any())
+ {
+ foreach (CodeProperty prop in parentClass.GetPropertiesOfKind(CodePropertyKind.Custom, CodePropertyKind.AdditionalData))
+ {
+ var required = prop.Type.IsNullable ? "" : "required ";
+
+ if (!conventions.ErrorClassPropertyExistsInSuperClass(prop))
+ {
+ writer.WriteLine($"{required}this.{prop.Name},");
+ }
+ }
+ }
+ }
+
+ private string DefaultDeserializerReturnInstance => $"";
+ private void WriteDeserializerBody(bool shouldHide, CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ if (parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForUnionType)
+ WriteDeserializerBodyForUnionModel(codeElement, parentClass, writer);
+ else if (parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForIntersectionType)
+ WriteDeserializerBodyForIntersectionModel(parentClass, writer);
+ else
+ WriteDeserializerBodyForInheritedModel(shouldHide, codeElement, parentClass, writer);
+ }
+ private void WriteDeserializerBodyForUnionModel(CodeMethod method, CodeClass parentClass, LanguageWriter writer)
+ {
+ var includeElse = false;
+ foreach (var otherPropName in parentClass
+ .GetPropertiesOfKind(CodePropertyKind.Custom)
+ .Where(static x => !x.ExistsInBaseType)
+ .Where(static x => x.Type is CodeType propertyType && !propertyType.IsCollection && propertyType.TypeDefinition is CodeClass)
+ .OrderBy(static x => x, CodePropertyTypeForwardComparer)
+ .ThenBy(static x => x.Name)
+ .Select(static x => x.Name))
+ {
+ writer.StartBlock($"{(includeElse ? "else " : string.Empty)}if({otherPropName} != null) {{");
+ writer.WriteLine($"return {otherPropName}!.{method.Name}();");
+ writer.CloseBlock();
+ if (!includeElse)
+ includeElse = true;
+ }
+ writer.WriteLine($"return {DefaultDeserializerReturnInstance}{{}};");
+ }
+ private const string DeserializerName = "deserializers";
+
+ private void WriteDeserializerBodyForIntersectionModel(CodeClass parentClass, LanguageWriter writer)
+ {
+
+ writer.WriteLine($"var {DeserializerName} = {DefaultDeserializerReturnInstance}{{}};");
+ var complexProperties = parentClass.GetPropertiesOfKind(CodePropertyKind.Custom)
+ .Where(static x => x.Type is CodeType propType && propType.TypeDefinition is CodeClass && !x.Type.IsCollection)
+ .ToArray();
+ foreach (CodeProperty prop in complexProperties)
+ {
+ writer.WriteLine($"if({prop.Name} != null){{{prop.Name}!.getFieldDeserializers().forEach((k,v) => {DeserializerName}.putIfAbsent(k, ()=>v));}}");
+ }
+ writer.WriteLine($"return {DeserializerName};");
+ }
+ private const string DeserializerVarName = "deserializerMap";
+ private void WriteDeserializerBodyForInheritedModel(bool shouldHide, CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
+ {
+ var fieldToSerialize = parentClass.GetPropertiesOfKind(CodePropertyKind.Custom, CodePropertyKind.ErrorMessageOverride).ToArray();
+ if (shouldHide)
+ {
+ writer.WriteLine($"var {DeserializerVarName} = " + "super.getFieldDeserializers();");
+ }
+ else
+ {
+ writer.WriteLine($"var {DeserializerVarName} = {DefaultDeserializerReturnInstance}{{}};");
+ }
+
+ if (fieldToSerialize.Length != 0)
+ {
+ fieldToSerialize
+ .Where(x => !x.ExistsInBaseType && !conventions.ErrorClassPropertyExistsInSuperClass(x))
+ .OrderBy(static x => x.Name)
+ .Select(x =>
+ $"{DeserializerVarName}['{x.WireName}'] = (node) => {x.Name} = node.{GetDeserializationMethodName(x.Type, codeElement)};")
+ .ToList()
+ .ForEach(x => writer.WriteLine(x));
+ }
+ writer.WriteLine($"return {DeserializerVarName};");
+ }
+ private string GetDeserializationMethodName(CodeTypeBase propType, CodeMethod method)
+ {
+ var isCollection = propType.CollectionKind != CodeTypeBase.CodeTypeCollectionKind.None;
+ var propertyType = conventions.GetTypeString(propType, method, false);
+ if (propType is CodeType currentType)
+ {
+ if (isCollection)
+ {
+ var collectionMethod = "";
+ if (currentType.TypeDefinition == null)
+ return $"getCollectionOfPrimitiveValues<{propertyType.TrimEnd(DartConventionService.NullableMarker)}>(){collectionMethod}";
+ else if (currentType.TypeDefinition is CodeEnum enumType)
+ {
+ var typeName = enumType.Name;
+ return $"getCollectionOfEnumValues<{typeName}>((stringValue) => {typeName}.values.where((enumVal) => enumVal.value == stringValue).firstOrNull)";
+ }
+ else
+ return $"getCollectionOfObjectValues<{propertyType}>({propertyType}.createFromDiscriminatorValue){collectionMethod}";
+ }
+ else if (currentType.TypeDefinition is CodeEnum enumType)
+ {
+ var typeName = enumType.Name;
+ return $"getEnumValue<{typeName}>((stringValue) => {typeName}.values.where((enumVal) => enumVal.value == stringValue).firstOrNull)";
+ }
+ }
+ return propertyType switch
+ {
+ "Iterable" => "getCollectionOfPrimitiveValues()",
+ "UuidValue" => "getGuidValue()",
+ "byte[]" => "getByteArrayValue()",
+ _ when conventions.IsPrimitiveType(propertyType) => $"get{propertyType.TrimEnd(DartConventionService.NullableMarker).ToFirstCharacterUpperCase()}Value()",
+ _ => $"getObjectValue<{propertyType.ToFirstCharacterUpperCase()}>({propertyType}.createFromDiscriminatorValue)",
+ };
+ }
+ protected void WriteRequestExecutorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass parentClass, bool isVoid, string returnTypeWithoutCollectionInformation, LanguageWriter writer)
+ {
+ ArgumentNullException.ThrowIfNull(codeElement);
+ ArgumentNullException.ThrowIfNull(requestParams);
+ ArgumentNullException.ThrowIfNull(parentClass);
+ ArgumentNullException.ThrowIfNull(writer);
+ if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
+
+ var generatorMethodName = parentClass
+ .Methods
+ .FirstOrDefault(x => x.IsOfKind(CodeMethodKind.RequestGenerator) && x.HttpMethod == codeElement.HttpMethod)
+ ?.Name;
+ var parametersList = new CodeParameter?[] { requestParams.requestBody, requestParams.requestContentType, requestParams.requestConfiguration }
+ .Select(static x => x?.Name).Where(static x => x != null).Aggregate(static (x, y) => $"{x}, {y}");
+ writer.WriteLine($"var requestInfo = {generatorMethodName}({parametersList});");
+ var errorMappingVarName = "{}";
+ if (codeElement.ErrorMappings.Any())
+ {
+ errorMappingVarName = "errorMapping";
+ writer.StartBlock($"final {errorMappingVarName} = >{{");
+ foreach (var errorMapping in codeElement.ErrorMappings.Where(errorMapping => errorMapping.Value.AllTypes.FirstOrDefault()?.TypeDefinition is CodeClass))
+ {
+ writer.WriteLine($"'{errorMapping.Key.ToUpperInvariant()}' : {conventions.GetTypeString(errorMapping.Value, codeElement, false)}.createFromDiscriminatorValue,");
+ }
+ writer.CloseBlock("};");
+ }
+ var returnTypeCodeType = codeElement.ReturnType as CodeType;
+ var returnTypeFactory = returnTypeCodeType?.TypeDefinition is CodeClass || (returnTypeCodeType != null && returnTypeCodeType.Name.Equals(KiotaBuilder.UntypedNodeName, StringComparison.OrdinalIgnoreCase))
+ ? $", {returnTypeWithoutCollectionInformation}.createFromDiscriminatorValue"
+ : null;
+ writer.WriteLine($"return await requestAdapter.{GetSendRequestMethodName(isVoid, codeElement, codeElement.ReturnType)}(requestInfo{returnTypeFactory}, {errorMappingVarName});");
+ }
+ private const string RequestInfoVarName = "requestInfo";
+ private void WriteRequestGeneratorBody(CodeMethod codeElement, RequestParams requestParams, CodeClass currentClass, LanguageWriter writer)
+ {
+ if (codeElement.HttpMethod == null) throw new InvalidOperationException("http method cannot be null");
+ if (currentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is not CodeProperty urlTemplateParamsProperty) throw new InvalidOperationException("path parameters property cannot be null");
+ if (currentClass.GetPropertyOfKind(CodePropertyKind.UrlTemplate) is not CodeProperty urlTemplateProperty) throw new InvalidOperationException("url template property cannot be null");
+
+ var operationName = codeElement.HttpMethod.ToString();
+ var urlTemplateValue = codeElement.HasUrlTemplateOverride ? $"'{codeElement.UrlTemplateOverride}'" : GetPropertyCall(urlTemplateProperty, "''");
+ writer.WriteLine($"var {RequestInfoVarName} = RequestInformation(httpMethod : HttpMethod.{operationName?.ToLowerInvariant()}, {urlTemplateProperty.Name} : {urlTemplateValue}, {urlTemplateParamsProperty.Name} : {GetPropertyCall(urlTemplateParamsProperty, "string.Empty")});");
+
+ if (requestParams.requestConfiguration != null && requestParams.requestConfiguration.Type is CodeType paramType)
+ {
+ var parameterClassName = paramType.GenericTypeParameterValues.First().Name;
+ writer.WriteLine($"{RequestInfoVarName}.configure<{parameterClassName}>({requestParams.requestConfiguration.Name}, () => {parameterClassName}());");
+ }
+
+ if (codeElement.ShouldAddAcceptHeader)
+ writer.WriteLine($"{RequestInfoVarName}.headers.put('Accept', '{codeElement.AcceptHeaderValue}');");
+ if (requestParams.requestBody != null)
+ {
+ var suffix = requestParams.requestBody.Type.IsCollection ? "Collection" : string.Empty;
+ if (requestParams.requestBody.Type.Name.Equals(conventions.StreamTypeName, StringComparison.OrdinalIgnoreCase))
+ {
+ if (requestParams.requestContentType is not null)
+ writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name}, {requestParams.requestContentType.Name});");
+ else if (!string.IsNullOrEmpty(codeElement.RequestBodyContentType))
+ writer.WriteLine($"{RequestInfoVarName}.setStreamContent({requestParams.requestBody.Name}, '{codeElement.RequestBodyContentType}');");
+ }
+ else if (currentClass.GetPropertyOfKind(CodePropertyKind.RequestAdapter) is CodeProperty requestAdapterProperty)
+ if (requestParams.requestBody.Type is CodeType bodyType && (bodyType.TypeDefinition is CodeClass || bodyType.Name.Equals("MultipartBody", StringComparison.OrdinalIgnoreCase)))
+ writer.WriteLine($"{RequestInfoVarName}.setContentFromParsable{suffix}({requestAdapterProperty.Name}, '{codeElement.RequestBodyContentType}', {requestParams.requestBody.Name});");
+ else
+ writer.WriteLine($"{RequestInfoVarName}.setContentFromScalar{suffix}({requestAdapterProperty.Name}, '{codeElement.RequestBodyContentType}', {requestParams.requestBody.Name});");
+ }
+
+ writer.WriteLine($"return {RequestInfoVarName};");
+ }
+ private static string GetPropertyCall(CodeProperty property, string defaultValue) => property == null ? defaultValue : $"{property.Name}";
+ private void WriteSerializerBody(bool shouldHide, CodeMethod method, CodeClass parentClass, LanguageWriter writer)
+ {
+ if (parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForUnionType)
+ WriteSerializerBodyForUnionModel(method, parentClass, writer);
+ else if (parentClass.DiscriminatorInformation.ShouldWriteDiscriminatorForIntersectionType)
+ WriteSerializerBodyForIntersectionModel(method, parentClass, writer);
+ else
+ WriteSerializerBodyForInheritedModel(shouldHide, method, parentClass, writer);
+
+ if (parentClass.GetPropertyOfKind(CodePropertyKind.AdditionalData) is CodeProperty additionalDataProperty)
+ writer.WriteLine($"writer.writeAdditionalData({additionalDataProperty.Name});");
+ }
+ private void WriteSerializerBodyForInheritedModel(bool shouldHide, CodeMethod method, CodeClass parentClass, LanguageWriter writer)
+ {
+ if (shouldHide)
+ writer.WriteLine("super.serialize(writer);");
+ foreach (var otherProp in parentClass
+ .GetPropertiesOfKind(CodePropertyKind.Custom, CodePropertyKind.ErrorMessageOverride)
+ .Where(x => !x.ExistsInBaseType && !x.ReadOnly && !conventions.ErrorClassPropertyExistsInSuperClass(x))
+ .OrderBy(static x => x.Name))
+ {
+ var serializationMethodName = GetSerializationMethodName(otherProp.Type, method);
+ var booleanValue = serializationMethodName == "writeBoolValue" ? "value:" : "";
+ var secondArgument = "";
+ if (otherProp.Type is CodeType currentType && currentType.TypeDefinition is CodeEnum enumType)
+ {
+ secondArgument = $", (e) => e?.value";
+ }
+ writer.WriteLine($"writer.{serializationMethodName}('{otherProp.WireName}', {booleanValue}{otherProp.Name}{secondArgument});");
+ }
+ }
+ private void WriteSerializerBodyForUnionModel(CodeMethod method, CodeClass parentClass, LanguageWriter writer)
+ {
+ var includeElse = false;
+ foreach (var otherProp in parentClass
+ .GetPropertiesOfKind(CodePropertyKind.Custom)
+ .Where(static x => !x.ExistsInBaseType)
+ .OrderBy(static x => x, CodePropertyTypeForwardComparer)
+ .ThenBy(static x => x.Name))
+ {
+ var serializationMethodName = GetSerializationMethodName(otherProp.Type, method);
+ var booleanValue = serializationMethodName == "writeBoolValue" ? "value:" : "";
+ writer.StartBlock($"{(includeElse ? "else " : string.Empty)}if({otherProp.Name} != null) {{");
+ writer.WriteLine($"writer.{GetSerializationMethodName(otherProp.Type, method)}(null, {booleanValue}{otherProp.Name});");
+ writer.CloseBlock();
+ if (!includeElse)
+ includeElse = true;
+ }
+ }
+ private void WriteSerializerBodyForIntersectionModel(CodeMethod method, CodeClass parentClass, LanguageWriter writer)
+ {
+ var includeElse = false;
+ foreach (var otherProp in parentClass
+ .GetPropertiesOfKind(CodePropertyKind.Custom)
+ .Where(static x => !x.ExistsInBaseType)
+ .Where(static x => x.Type is not CodeType propertyType || propertyType.IsCollection || propertyType.TypeDefinition is not CodeClass)
+ .OrderBy(static x => x, CodePropertyTypeBackwardComparer)
+ .ThenBy(static x => x.Name))
+ {
+ var serializationMethodName = GetSerializationMethodName(otherProp.Type, method);
+ var booleanValue = serializationMethodName == "writeBoolValue" ? "value:" : "";
+ writer.StartBlock($"{(includeElse ? "else " : string.Empty)}if({otherProp.Name} != null) {{");
+ writer.WriteLine($"writer.{GetSerializationMethodName(otherProp.Type, method)}(null, {booleanValue}{otherProp.Name});");
+ writer.CloseBlock();
+ if (!includeElse)
+ includeElse = true;
+ }
+ var complexProperties = parentClass.GetPropertiesOfKind(CodePropertyKind.Custom)
+ .Where(static x => x.Type is CodeType propType && propType.TypeDefinition is CodeClass && !x.Type.IsCollection)
+ .ToArray();
+ if (complexProperties.Length != 0)
+ {
+ if (includeElse)
+ {
+ writer.StartBlock("else {");
+ }
+ var firstPropertyName = complexProperties.First().Name;
+ var propertiesNames = complexProperties.Skip(1).Any() ? complexProperties.Skip(1)
+ .Select(static x => x.Name)
+ .OrderBy(static x => x)
+ .Aggregate(static (x, y) => $"{x}, {y}") : string.Empty;
+ var propertiesList = string.IsNullOrEmpty(propertiesNames) ? "" : $", [{propertiesNames}]";
+ writer.WriteLine($"writer.{GetSerializationMethodName(complexProperties.First().Type, method)}(null, {firstPropertyName}{propertiesList});");
+ if (includeElse)
+ {
+ writer.CloseBlock();
+ }
+ }
+ }
+
+ protected string GetSendRequestMethodName(bool isVoid, CodeElement currentElement, CodeTypeBase returnType)
+ {
+ ArgumentNullException.ThrowIfNull(returnType);
+ var returnTypeName = conventions.GetTypeString(returnType, currentElement, false);
+ var isStream = conventions.StreamTypeName.Equals(returnTypeName, StringComparison.OrdinalIgnoreCase);
+ var isEnum = returnType is CodeType codeType && codeType.TypeDefinition is CodeEnum;
+ if (isVoid) return "sendNoContent";
+ else if (isStream || conventions.IsPrimitiveType(returnTypeName) || isEnum)
+ if (returnType.IsCollection)
+ return $"sendPrimitiveCollection<{returnTypeName.TrimEnd('?')}>";
+ else
+ return $"sendPrimitive<{returnTypeName}>";
+ else if (returnType.IsCollection) return $"sendCollection<{returnTypeName}>";
+ else if (returnType.Name.EqualsIgnoreCase("binary")) return "sendPrimitiveCollection";
+ else return $"send<{returnTypeName}>";
+ }
+ private void WriteMethodDocumentation(CodeMethod code, LanguageWriter writer)
+ {
+ conventions.WriteLongDescription(code, writer);
+ foreach (var paramWithDescription in code.Parameters
+ .Where(static x => x.Documentation.DescriptionAvailable)
+ .OrderBy(static x => x.Name, StringComparer.OrdinalIgnoreCase))
+ conventions.WriteParameterDescription(paramWithDescription, writer);
+ conventions.WriteDeprecationAttribute(code, writer);
+ }
+ private static readonly BaseCodeParameterOrderComparer parameterOrderComparer = new();
+ private static string GetBaseSuffix(bool isConstructor, bool inherits, CodeClass parentClass, CodeMethod currentMethod)
+ {
+ if (isConstructor && inherits)
+ {
+ if (parentClass.IsOfKind(CodeClassKind.RequestBuilder) && parentClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.UrlTemplate) is CodeProperty urlTemplateProperty &&
+ !string.IsNullOrEmpty(urlTemplateProperty.DefaultValue))
+ {
+ var thirdParameterName = string.Empty;
+ if (currentMethod.Parameters.OfKind(CodeParameterKind.PathParameters) is CodeParameter pathParametersParameter)
+ thirdParameterName = $", {pathParametersParameter.Name}";
+ else if (currentMethod.Parameters.OfKind(CodeParameterKind.RawUrl) is CodeParameter rawUrlParameter)
+ thirdParameterName = $", {{RequestInformation.rawUrlKey : {rawUrlParameter.Name}}}";
+ else if (parentClass.Properties.FirstOrDefaultOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProperty && !string.IsNullOrEmpty(pathParametersProperty.DefaultValue))
+ thirdParameterName = $", {pathParametersProperty.DefaultValue}";
+ if (currentMethod.Parameters.OfKind(CodeParameterKind.RequestAdapter) is CodeParameter requestAdapterParameter)
+ {
+ return $" : super({requestAdapterParameter.Name}, {urlTemplateProperty.DefaultValue}{thirdParameterName})";
+ }
+ else if (parentClass.StartBlock?.Inherits?.Name?.Contains("CliRequestBuilder", StringComparison.Ordinal) == true)
+ {
+ // CLI uses a different base class.
+ return $" : super({urlTemplateProperty.DefaultValue}{thirdParameterName})";
+ }
+ }
+ return " : super()";
+ }
+ else if (isConstructor && parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.AdditionalData)).Any() && !parentClass.IsErrorDefinition && !parentClass.Properties.Where(static x => x.IsOfKind(CodePropertyKind.BackingStore)).Any())
+ {
+ return " : ";
+ }
+
+ return string.Empty;
+ }
+ private void WriteMethodPrototype(CodeMethod code, CodeClass parentClass, LanguageWriter writer, string returnType, bool inherits, bool isVoid)
+ {
+ var staticModifier = code.IsStatic ? "static " : string.Empty;
+ if (code.IsOfKind(CodeMethodKind.Serializer, CodeMethodKind.Deserializer, CodeMethodKind.QueryParametersMapper) || code.IsOfKind(CodeMethodKind.Custom))
+ {
+ writer.WriteLine("@override");
+ }
+
+ var genericTypeSuffix = code.IsAsync && !isVoid ? ">" : string.Empty;
+ var isConstructor = code.IsOfKind(CodeMethodKind.Constructor, CodeMethodKind.ClientConstructor, CodeMethodKind.RawUrlConstructor);
+ var voidCorrectedTaskReturnType = code.IsAsync && isVoid ? "void" : returnType;
+ var async = code.IsAsync ? " async" : string.Empty;
+ if (code.ReturnType.IsArray && code.IsOfKind(CodeMethodKind.RequestExecutor))
+ voidCorrectedTaskReturnType = $"IEnumerable<{voidCorrectedTaskReturnType.StripArraySuffix()}>";
+ else if (code.IsOfKind(CodeMethodKind.RequestExecutor) && code.IsAsync)
+ voidCorrectedTaskReturnType = $"Future<{voidCorrectedTaskReturnType.StripArraySuffix()}>";
+ // TODO: Task type should be moved into the refiner
+ var completeReturnType = isConstructor ?
+ string.Empty : voidCorrectedTaskReturnType + " ";
+ var baseSuffix = GetBaseSuffix(isConstructor, inherits, parentClass, code);
+ var parameters = string.Join(", ", code.Parameters.OrderBy(static x => x, parameterOrderComparer).Select(p => conventions.GetParameterSignature(p, code)).ToList());
+ var methodName = GetMethodName(code, parentClass, isConstructor);
+ var includeNullableReferenceType = code.IsOfKind(CodeMethodKind.RequestExecutor, CodeMethodKind.RequestGenerator);
+ var openingBracket = baseSuffix.Equals(" : ", StringComparison.Ordinal) ? "" : "{";
+ var closingparenthesis = (isConstructor && parentClass.IsErrorDefinition) ? string.Empty : ")";
+ // Constuctors (except for ClientConstructor) don't need a body but a closing statement
+ if (HasEmptyConstructorBody(code, parentClass, isConstructor))
+ {
+ openingBracket = ";";
+ }
+
+ if (includeNullableReferenceType)
+ {
+ var completeReturnTypeWithNullable = isConstructor || string.IsNullOrEmpty(genericTypeSuffix) ? completeReturnType : $"{completeReturnType[..^2].TrimEnd('?')}?{genericTypeSuffix} ";
+ var nullableParameters = string.Join(", ", code.Parameters.Order(parameterOrderComparer)
+ .Select(p => p.IsOfKind(CodeParameterKind.RequestConfiguration) ?
+ $"[{GetParameterSignatureWithNullableRefType(p, code)}]" :
+ conventions.GetParameterSignature(p, code))
+ .ToList());
+ writer.WriteLine($"{staticModifier}{completeReturnTypeWithNullable}{conventions.GetAccessModifier(code.Access)}{methodName}({nullableParameters}){baseSuffix}{async} {{");
+ }
+ else if (parentClass.IsOfKind(CodeClassKind.Model) && code.IsOfKind(CodeMethodKind.Custom) && code.Name.EqualsIgnoreCase("copyWith"))
+ {
+ var parentParameters = "int? statusCode, String? message, Map>? responseHeaders, Iterable