diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index 50d2e9b645..307dc63e9b 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -67,6 +67,7 @@ jobs:
- "./tests/Kiota.Builder.IntegrationTests/InheritingErrors.yaml"
- "./tests/Kiota.Builder.IntegrationTests/NoUnderscoresInModel.yaml"
- "./tests/Kiota.Builder.IntegrationTests/ToDoApi.yaml"
+ - "./tests/Kiota.Builder.IntegrationTests/GeneratesUritemplateHints.yaml"
- "oas::petstore"
- "apisguru::twitter.com:current"
- "apisguru::notion.com"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 31afa093f3..8f82084021 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+- Java - Self-extraction of query parameters instead of using reflection. [#3965](https://github.com/microsoft/kiota/issues/3965)
- Fixed a bug where the discriminator validation rule would report false positives on nullable union types.
- Fixed a bug where constructors and model names where clashing in Go. [#3920](https://github.com/microsoft/kiota/issues/3920)
- Fixed a bug where the order of enum declaration might results in a missing enum type. [#3935](https://github.com/microsoft/kiota/issues/3935)
diff --git a/it/config.json b/it/config.json
index 43d7324e87..8b9113e4d2 100644
--- a/it/config.json
+++ b/it/config.json
@@ -70,6 +70,9 @@
}
]
},
+ "./tests/Kiota.Builder.IntegrationTests/GeneratesUritemplateHints.yaml": {
+ "MockServerITFolder": "query-params"
+ },
"apisguru::notion.com": {
"ExcludePatterns": [
{
diff --git a/it/exec-cmd.ps1 b/it/exec-cmd.ps1
index 61d6461320..bae550fac2 100755
--- a/it/exec-cmd.ps1
+++ b/it/exec-cmd.ps1
@@ -63,8 +63,14 @@ if ($null -ne $descriptionValue) {
}
}
+$mockServerTest = false
+$itTestPath = Join-Path -Path $testPath -ChildPath $mockSeverITFolder
+if (Test-Path -Path $itTestPath) {
+ $mockServerTest = true
+}
+
# Start MockServer if needed
-if (!([string]::IsNullOrEmpty($mockSeverITFolder))) {
+if ($mockServerTest) {
# Kill any leftover MockServer
Kill-MockServer
Push-Location $mockServerPath
@@ -80,8 +86,7 @@ if (!([string]::IsNullOrEmpty($mockSeverITFolder))) {
Push-Location $testPath
if ($language -eq "csharp") {
- if (!([string]::IsNullOrEmpty($mockSeverITFolder))) {
- $itTestPath = Join-Path -Path $testPath -ChildPath $mockSeverITFolder
+ if ($mockServerTest) {
Push-Location $itTestPath
$itTestPathSources = Join-Path -Path $testPath -ChildPath "client"
@@ -104,8 +109,7 @@ if ($language -eq "csharp") {
}
}
elseif ($language -eq "java") {
- if (!([string]::IsNullOrEmpty($mockSeverITFolder))) {
- $itTestPath = Join-Path -Path $testPath -ChildPath $mockSeverITFolder
+ if ($mockServerTest) {
Push-Location $itTestPath
$itTestPathSources = Join-Path -Path $testPath -ChildPath "src" -AdditionalChildPath "*"
@@ -128,8 +132,7 @@ elseif ($language -eq "java") {
}
}
elseif ($language -eq "go") {
- if (!([string]::IsNullOrEmpty($mockSeverITFolder))) {
- $itTestPath = Join-Path -Path $testPath -ChildPath $mockSeverITFolder
+ if ($mockServerTest) {
Push-Location $itTestPath
$itTestPathSources = Join-Path -Path $testPath -ChildPath "client"
@@ -182,8 +185,7 @@ elseif ($language -eq "python") {
mypy integration_test
} -ErrorAction Stop
- if (!([string]::IsNullOrEmpty($mockSeverITFolder))) {
- $itTestPath = Join-Path -Path $testPath -ChildPath $mockSeverITFolder
+ if ($mockServerTest) {
Push-Location $itTestPath
$itTestPathSources = Join-Path -Path $testPath -ChildPath "integration_test" -AdditionalChildPath "client"
diff --git a/it/java/.gitignore b/it/java/.gitignore
index 01e7d50b8c..508a236b20 100644
--- a/it/java/.gitignore
+++ b/it/java/.gitignore
@@ -2,3 +2,6 @@
target
/basic/src/main
/basic/src/kiota-lock.json
+/query-params/src/main
+/query-params/src/kiota-lock.json
+
diff --git a/it/java/basic/pom.xml b/it/java/basic/pom.xml
index f44eed28e4..88757938f6 100644
--- a/it/java/basic/pom.xml
+++ b/it/java/basic/pom.xml
@@ -15,7 +15,7 @@
UTF-8
UTF-8
- 0.11.0
+ 0.12.1
diff --git a/it/java/pom.xml b/it/java/pom.xml
index 269c230718..f7135ceb7e 100644
--- a/it/java/pom.xml
+++ b/it/java/pom.xml
@@ -15,7 +15,7 @@
UTF-8
UTF-8
- 0.11.0
+ 0.12.1
diff --git a/it/java/query-params/pom.xml b/it/java/query-params/pom.xml
new file mode 100644
index 0000000000..88757938f6
--- /dev/null
+++ b/it/java/query-params/pom.xml
@@ -0,0 +1,74 @@
+
+
+ 4.0.0
+ io.kiota
+ kiota-basic-test
+ 0.1.0-SNAPSHOT
+
+
+ 11
+ 11
+ 11
+ UTF-8
+ UTF-8
+
+ 0.12.1
+
+
+
+
+ com.microsoft.kiota
+ microsoft-kiota-abstractions
+ ${kiota-java.version}
+
+
+ com.microsoft.kiota
+ microsoft-kiota-serialization-json
+ ${kiota-java.version}
+
+
+ com.microsoft.kiota
+ microsoft-kiota-serialization-text
+ ${kiota-java.version}
+
+
+ com.microsoft.kiota
+ microsoft-kiota-serialization-form
+ ${kiota-java.version}
+
+
+ com.microsoft.kiota
+ microsoft-kiota-serialization-multipart
+ ${kiota-java.version}
+
+
+ com.microsoft.kiota
+ microsoft-kiota-http-okHttp
+ ${kiota-java.version}
+
+
+ jakarta.annotation
+ jakarta.annotation-api
+ 2.1.1
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.9.2
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M9
+
+
+
+
\ No newline at end of file
diff --git a/it/java/query-params/src/test/java/BasicAPITest.java b/it/java/query-params/src/test/java/BasicAPITest.java
new file mode 100644
index 0000000000..5eafb1d43a
--- /dev/null
+++ b/it/java/query-params/src/test/java/BasicAPITest.java
@@ -0,0 +1,52 @@
+import apisdk.ApiClient;
+import com.microsoft.kiota.ApiException;
+import com.microsoft.kiota.authentication.AnonymousAuthenticationProvider;
+import com.microsoft.kiota.http.OkHttpRequestAdapter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class BasicAPITest {
+
+ @Test
+ void includesSomeQueryParameter() throws Exception {
+ var client = new ApiClient(new OkHttpRequestAdapter(new AnonymousAuthenticationProvider()));
+
+ var reqInf = client.api().something().v1().toGetRequestInformation(config -> {
+ config.queryParameters.startDateTime = "START";
+ });
+ reqInf.pathParameters.put("baseurl", "http://test");
+
+ assertEquals("http://test/api/something/v1?startDateTime=START", reqInf.getUri().toString());
+ }
+
+ @Test
+ void includesSomeOtherQueryParameter() throws Exception {
+ var client = new ApiClient(new OkHttpRequestAdapter(new AnonymousAuthenticationProvider()));
+
+ var reqInf = client.api().something().v1().toGetRequestInformation(config -> {
+ config.queryParameters.endDateTime = "END";
+ });
+ reqInf.pathParameters.put("baseurl", "http://test");
+
+ assertEquals("http://test/api/something/v1?EndDateTime=END", reqInf.getUri().toString());
+ }
+
+ @Test
+ void includesAllTheQueryParameters() throws Exception {
+ var client = new ApiClient(new OkHttpRequestAdapter(new AnonymousAuthenticationProvider()));
+
+ var reqInf = client.api().something().v1().toGetRequestInformation(config -> {
+ config.queryParameters.startDateTime = "START";
+ config.queryParameters.endDateTime = "END";
+ });
+ reqInf.pathParameters.put("baseurl", "http://test");
+
+ assertEquals("http://test/api/something/v1?startDateTime=START&EndDateTime=END", reqInf.getUri().toString());
+ }
+
+}
diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs
index 7dc512de54..33fe45e1d5 100644
--- a/src/Kiota.Builder/Refiners/JavaRefiner.cs
+++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs
@@ -7,6 +7,7 @@
using Kiota.Builder.Configuration;
using Kiota.Builder.Extensions;
using Kiota.Builder.Writers.Java;
+using Microsoft.Kiota.Abstractions;
namespace Kiota.Builder.Refiners;
public class JavaRefiner : CommonLanguageRefiner, ILanguageRefiner
@@ -18,6 +19,7 @@ public override Task Refine(CodeNamespace generatedCode, CancellationToken cance
{
cancellationToken.ThrowIfCancellationRequested();
CorrectCommonNames(generatedCode);
+ AddQueryParameterExtractorMethod(generatedCode);
MoveRequestBuilderPropertiesToBaseType(generatedCode,
new CodeUsing
{
@@ -224,11 +226,11 @@ private static void AddEnumSetImport(CodeElement currentElement)
private static readonly AdditionalUsingEvaluator[] defaultUsingEvaluators = {
new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.RequestAdapter),
AbstractionsNamespaceName, "RequestAdapter"),
- new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.PathParameters),
+ new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.PathParameters) || x is CodeMethod method && method.IsOfKind(CodeMethodKind.QueryParametersMapper),
"java.util", "HashMap"),
new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator),
AbstractionsNamespaceName, "RequestInformation", "RequestOption", "HttpMethod"),
- new (static x => x is CodeMethod method && method.IsOfKind(CodeMethodKind.RequestGenerator),
+ new (static x => x is CodeMethod method && (method.IsOfKind(CodeMethodKind.RequestGenerator) || method.IsOfKind(CodeMethodKind.QueryParametersMapper)),
"java.util", "Collection", "Map"),
new (static x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.Model),
SerializationNamespaceName, "Parsable"),
@@ -257,8 +259,8 @@ private static void AddEnumSetImport(CodeElement currentElement)
x is CodeMethod method && "decimal".Equals(method.ReturnType.Name, StringComparison.OrdinalIgnoreCase) ||
x is CodeParameter para && "decimal".Equals(para.Type.Name, StringComparison.OrdinalIgnoreCase),
"java.math", "BigDecimal"),
- new (static x => x is CodeProperty prop && prop.IsOfKind(CodePropertyKind.QueryParameter) && !string.IsNullOrEmpty(prop.SerializationName),
- AbstractionsNamespaceName, "QueryParameter"),
+ new (static x => x is CodeClass @class && @class.IsOfKind(CodeClassKind.QueryParameters),
+ AbstractionsNamespaceName, "QueryParameters"),
new (static x => x is CodeClass @class && @class.OriginalComposedType is CodeIntersectionType intersectionType && intersectionType.Types.Any(static y => !y.IsExternal),
SerializationNamespaceName, "ParseNodeHelper"),
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)),
@@ -512,4 +514,35 @@ private static void LowerCaseNamespaceNames(CodeElement currentElement)
CrawlTree(currentElement, LowerCaseNamespaceNames);
}
}
+
+ private void AddQueryParameterExtractorMethod(CodeElement currentElement, string methodName = "toQueryParameters")
+ {
+ if (currentElement is CodeClass currentClass &&
+ currentClass.IsOfKind(CodeClassKind.QueryParameters))
+ {
+ currentClass.StartBlock.AddImplements(new CodeType
+ {
+ IsExternal = true,
+ Name = "QueryParameters"
+ });
+ 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()
+ {
+ Description = "Extracts the query parameters into a map for the URI template parsing.",
+ },
+ });
+ }
+ CrawlTree(currentElement, x => AddQueryParameterExtractorMethod(x, methodName));
+ }
}
diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs
index 0073f68b84..55c24cb677 100644
--- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs
+++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs
@@ -85,6 +85,9 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri
case CodeMethodKind.ErrorMessageOverride:
WriteErrorMethodOverride(parentClass, writer);
break;
+ case CodeMethodKind.QueryParametersMapper:
+ WriteQueryParametersExtractorBody(codeElement, writer, parentClass);
+ break;
case CodeMethodKind.ComposedTypeMarker:
throw new InvalidOperationException("ComposedTypeMarker is not required as interface is explicitly implemented.");
default:
@@ -402,6 +405,20 @@ private void WriteGetterBody(CodeMethod codeElement, LanguageWriter writer, Code
else
writer.WriteLine($"return this.{backingStore.Name}.get(\"{codeElement.AccessedProperty?.Name}\");");
}
+ private void WriteQueryParametersExtractorBody(CodeMethod codeElement, LanguageWriter writer, CodeClass parentClass)
+ {
+ writer.WriteLine("final Map allQueryParams = new HashMap();");
+ var allQueryParams = parentClass
+ .GetPropertiesOfKind(CodePropertyKind.QueryParameter)
+ .OrderBy(static x => x, CodePropertyTypeForwardComparer)
+ .ThenBy(static x => x.Name);
+ foreach (var queryParam in allQueryParams)
+ {
+ var keyValue = queryParam.IsNameEscaped ? queryParam.SerializationName : queryParam.Name;
+ writer.WriteLine($"allQueryParams.put(\"{keyValue}\", {queryParam.Name});");
+ }
+ writer.WriteLine("return allQueryParams;");
+ }
private void WriteIndexerBody(CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer, string returnType)
{
if (parentClass.GetPropertyOfKind(CodePropertyKind.PathParameters) is CodeProperty pathParametersProperty && codeElement.OriginalIndexer != null)
diff --git a/src/Kiota.Builder/Writers/Java/CodePropertyWriter.cs b/src/Kiota.Builder/Writers/Java/CodePropertyWriter.cs
index 8fd03c757e..61dc35b4a1 100644
--- a/src/Kiota.Builder/Writers/Java/CodePropertyWriter.cs
+++ b/src/Kiota.Builder/Writers/Java/CodePropertyWriter.cs
@@ -27,9 +27,6 @@ public override void WriteCodeElement(CodeProperty codeElement, LanguageWriter w
conventions.AddRequestBuilderBody(parentClass, returnType, writer);
writer.CloseBlock();
break;
- case CodePropertyKind.QueryParameter when codeElement.IsNameEscaped:
- writer.WriteLine($"@QueryParameter(name = \"{codeElement.SerializationName}\")");
- goto default;
case CodePropertyKind.Headers or CodePropertyKind.Options when !string.IsNullOrEmpty(codeElement.DefaultValue):
defaultValue = $" = {codeElement.DefaultValue}";
goto default;
diff --git a/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs b/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs
index 33aecaaccd..2e73a6efc5 100644
--- a/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs
+++ b/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs
@@ -183,7 +183,7 @@ public async Task GeneratesUritemplateHints(GenerationLanguage language)
Assert.Contains("`uriparametername:\"startDateTime\"`", fullText);
break;
case GenerationLanguage.Java:
- Assert.Contains("@QueryParameter(name = \"EndDateTime\")", fullText);
+ Assert.Contains("allQueryParams.put(\"EndDateTime\", endDateTime)", fullText);
break;
case GenerationLanguage.PHP:
Assert.Contains("@QueryParameter(\"EndDateTime\")", fullText);
diff --git a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs
index 8a3b79447f..8a81d8ea2f 100644
--- a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs
@@ -1964,6 +1964,28 @@ public void WritesApiConstructorWithBackingStore()
Assert.Contains("enableBackingStore", result);
}
[Fact]
+ public void WritesQueryParametersExtractor()
+ {
+ setup();
+ method.Kind = CodeMethodKind.QueryParametersMapper;
+ var defaultValue = "\"someVal\"";
+ var propName = "propWithDefaultValue";
+ parentClass.Kind = CodeClassKind.QueryParameters;
+ parentClass.AddProperty(new CodeProperty
+ {
+ Name = propName,
+ DefaultValue = defaultValue,
+ Kind = CodePropertyKind.QueryParameter,
+ Type = new CodeType
+ {
+ Name = "String"
+ }
+ });
+ writer.Write(method);
+ var result = tw.ToString();
+ Assert.Contains("allQueryParams.put(\"propWithDefaultValue\", propWithDefaultValue);", result);
+ }
+ [Fact]
public async Task AccessorsTargetingEscapedPropertiesAreNotEscapedThemselves()
{
setup();
diff --git a/tests/Kiota.Builder.Tests/Writers/Java/CodePropertyWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodePropertyWriterTests.cs
index dcd3b5e988..d128067280 100644
--- a/tests/Kiota.Builder.Tests/Writers/Java/CodePropertyWriterTests.cs
+++ b/tests/Kiota.Builder.Tests/Writers/Java/CodePropertyWriterTests.cs
@@ -105,15 +105,6 @@ public void WritesNonNull()
Assert.Contains("@jakarta.annotation.Nonnull", result);
}
[Fact]
- public void WritesSerializationAnnotation()
- {
- property.Kind = CodePropertyKind.QueryParameter;
- property.SerializationName = "someserializationname";
- writer.Write(property);
- var result = tw.ToString();
- Assert.Contains("@QueryParameter(name = \"someserializationname\")", result);
- }
- [Fact]
public void WritesCollectionFlagEnumsAsOneDimensionalArray()
{
property.Kind = CodePropertyKind.Custom;