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;