From fabe8e82df3ec044f543a9c7da68ace9b728e7db Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 11 Dec 2025 17:45:27 +0800 Subject: [PATCH 1/3] Support QUERY method We can't name it to `QUERY` due to the case-insensitive file systems on MacOS and Window. Should we rename the annotation to `SEARCH` or something else? --- CHANGELOG.md | 1 + .../java/retrofit2/RequestFactoryTest.java | 17 ++++++++ .../main/java/retrofit2/RequestFactory.java | 3 ++ .../src/main/java/retrofit2/Retrofit.java | 5 ++- .../java/retrofit2/http/QUERY_METHOD.java | 39 +++++++++++++++++++ 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 retrofit/src/main/java/retrofit2/http/QUERY_METHOD.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 582b98174c..ac1cc19071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Add explicit keep rules for RxJava `Result` types to prevent their generic information from being removed. - Add `allowoptimization` flags for most kept types. - Add `Invocation.annotationUrl` which returns the original URL from the method annotation. + - Support `QUERY` method (annotate your methods with `@QUERY_METHOD`). **Changed** diff --git a/retrofit/java-test/src/test/java/retrofit2/RequestFactoryTest.java b/retrofit/java-test/src/test/java/retrofit2/RequestFactoryTest.java index b4eae2ce2d..30bbfc3f23 100644 --- a/retrofit/java-test/src/test/java/retrofit2/RequestFactoryTest.java +++ b/retrofit/java-test/src/test/java/retrofit2/RequestFactoryTest.java @@ -60,6 +60,7 @@ import retrofit2.http.Part; import retrofit2.http.PartMap; import retrofit2.http.Path; +import retrofit2.http.QUERY_METHOD; import retrofit2.http.Query; import retrofit2.http.QueryMap; import retrofit2.http.QueryName; @@ -1005,6 +1006,22 @@ Call method() { assertThat(request.body()).isNull(); } + @Test + public void query() { + class Example { + @QUERY_METHOD("/foo/bar/") // + Call method(@Body RequestBody body) { + return null; + } + } + RequestBody body = RequestBody.create(TEXT_PLAIN, "hi"); + Request request = buildRequest(Example.class, body); + assertThat(request.method()).isEqualTo("QUERY"); + assertThat(request.headers().size()).isEqualTo(0); + assertThat(request.url().toString()).isEqualTo("http://example.com/foo/bar/"); + assertBody(request.body(), "hi"); + } + @Test public void getWithPathParam() { class Example { diff --git a/retrofit/src/main/java/retrofit2/RequestFactory.java b/retrofit/src/main/java/retrofit2/RequestFactory.java index 55444e7a89..20e13daca9 100644 --- a/retrofit/src/main/java/retrofit2/RequestFactory.java +++ b/retrofit/src/main/java/retrofit2/RequestFactory.java @@ -56,6 +56,7 @@ import retrofit2.http.Part; import retrofit2.http.PartMap; import retrofit2.http.Path; +import retrofit2.http.QUERY_METHOD; import retrofit2.http.Query; import retrofit2.http.QueryMap; import retrofit2.http.QueryName; @@ -244,6 +245,8 @@ private void parseMethodAnnotation(Annotation annotation) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true); } else if (annotation instanceof OPTIONS) { parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false); + } else if (annotation instanceof QUERY_METHOD) { + parseHttpMethodAndPath("QUERY", ((QUERY_METHOD) annotation).value(), true); } else if (annotation instanceof HTTP) { HTTP http = (HTTP) annotation; parseHttpMethodAndPath(http.method(), http.path(), http.hasBody()); diff --git a/retrofit/src/main/java/retrofit2/Retrofit.java b/retrofit/src/main/java/retrofit2/Retrofit.java index 9e5a5bd561..af6b64f261 100644 --- a/retrofit/src/main/java/retrofit2/Retrofit.java +++ b/retrofit/src/main/java/retrofit2/Retrofit.java @@ -111,8 +111,9 @@ public final class Retrofit { *

The relative path for a given method is obtained from an annotation on the method describing * the request type. The built-in methods are {@link retrofit2.http.GET GET}, {@link * retrofit2.http.PUT PUT}, {@link retrofit2.http.POST POST}, {@link retrofit2.http.PATCH PATCH}, - * {@link retrofit2.http.HEAD HEAD}, {@link retrofit2.http.DELETE DELETE} and {@link - * retrofit2.http.OPTIONS OPTIONS}. You can use a custom HTTP method with {@link HTTP @HTTP}. For + * {@link retrofit2.http.HEAD HEAD}, {@link retrofit2.http.DELETE DELETE}, + * {@link retrofit2.http.OPTIONS OPTIONS}, and {@link retrofit2.http.QUERY_METHOD QUERY_METHOD}. + * You can use a custom HTTP method with {@link HTTP @HTTP}. For * a dynamic URL, omit the path on the annotation and annotate the first parameter with {@link * Url @Url}. * diff --git a/retrofit/src/main/java/retrofit2/http/QUERY_METHOD.java b/retrofit/src/main/java/retrofit2/http/QUERY_METHOD.java new file mode 100644 index 0000000000..257f146a28 --- /dev/null +++ b/retrofit/src/main/java/retrofit2/http/QUERY_METHOD.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2025 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package retrofit2.http; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import okhttp3.HttpUrl; + +/** Make a QUERY request. */ +@Documented +@Target(METHOD) +@Retention(RUNTIME) +public @interface QUERY_METHOD { + /** + * A relative or absolute path, or full URL of the endpoint. This value is optional if the first + * parameter of the method is annotated with {@link Url @Url}. + * + *

See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how + * this is resolved against a base URL to create the full endpoint URL. + */ + String value() default ""; +} From c6cb5ee4916ba896e2cdb3343aa04a00acb538ee Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Dec 2025 10:24:46 +0800 Subject: [PATCH 2/3] Add extra sourceSet and move QUERY_METHOD to QUERY --- CHANGELOG.md | 2 +- retrofit/build.gradle | 31 +++++++++++++------ .../java/retrofit2/RequestFactoryTest.java | 4 +-- .../retrofit2/http/QUERY.java} | 2 +- .../main/java/retrofit2/RequestFactory.java | 6 ++-- .../src/main/java/retrofit2/Retrofit.java | 2 +- 6 files changed, 30 insertions(+), 17 deletions(-) rename retrofit/src/main/{java/retrofit2/http/QUERY_METHOD.java => extra/retrofit2/http/QUERY.java} (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac1cc19071..a305cf24d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - Add explicit keep rules for RxJava `Result` types to prevent their generic information from being removed. - Add `allowoptimization` flags for most kept types. - Add `Invocation.annotationUrl` which returns the original URL from the method annotation. - - Support `QUERY` method (annotate your methods with `@QUERY_METHOD`). + - Support `QUERY` method. **Changed** diff --git a/retrofit/build.gradle b/retrofit/build.gradle index 4e83fe8e03..43ce931f58 100644 --- a/retrofit/build.gradle +++ b/retrofit/build.gradle @@ -2,17 +2,25 @@ apply plugin: 'java-library' apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'com.vanniktech.maven.publish' -def addMultiReleaseSourceSet(int version) { - def sourceSet = sourceSets.create("java$version") - sourceSet.java.srcDir("src/main/java$version") +def addExtraSourceSet(String name) { + def sourceSet = sourceSets.create(name) { + java.srcDir("src/main/$name") + } + + // Propagate dependencies to be visible to this source set. + configurations.getByName("${name}Implementation").extendsFrom(configurations.getByName('implementation')) + configurations.getByName("${name}Api").extendsFrom(configurations.getByName('api')) + configurations.getByName("${name}CompileOnly").extendsFrom(configurations.getByName('compileOnly')) + + return sourceSet +} - // Propagate dependencies to be visible to this version's source set. - configurations.getByName("java${version}Implementation").extendsFrom(configurations.getByName('implementation')) - configurations.getByName("java${version}Api").extendsFrom(configurations.getByName('api')) - configurations.getByName("java${version}CompileOnly").extendsFrom(configurations.getByName('compileOnly')) +def addMultiReleaseSourceSet(int version) { + def name = "java${version}" + def sourceSet = addExtraSourceSet(name) // Allow types in the main source set to be visible to this version's source set. - dependencies.add("java${version}Implementation", sourceSets.getByName("main").output) + dependencies.add("${name}Implementation", sourceSets.getByName("main").output) tasks.named("compileJava${version}Java", JavaCompile) { javaCompiler = javaToolchains.compilerFor { @@ -31,9 +39,12 @@ def addMultiReleaseSourceSet(int version) { addMultiReleaseSourceSet(14) addMultiReleaseSourceSet(16) +def extra = addExtraSourceSet('extra') + dependencies { api libs.okhttp.client + compileOnly extra.output compileOnly libs.android compileOnly libs.kotlinx.coroutines @@ -45,7 +56,9 @@ javadoc { exclude('retrofit2/internal/**') } -jar { +tasks.named('jar', Jar) { + from(extra.output) + manifest { attributes 'Automatic-Module-Name': 'retrofit2' attributes 'Multi-Release': 'true' diff --git a/retrofit/java-test/src/test/java/retrofit2/RequestFactoryTest.java b/retrofit/java-test/src/test/java/retrofit2/RequestFactoryTest.java index 30bbfc3f23..8e9f10e281 100644 --- a/retrofit/java-test/src/test/java/retrofit2/RequestFactoryTest.java +++ b/retrofit/java-test/src/test/java/retrofit2/RequestFactoryTest.java @@ -60,7 +60,7 @@ import retrofit2.http.Part; import retrofit2.http.PartMap; import retrofit2.http.Path; -import retrofit2.http.QUERY_METHOD; +import retrofit2.http.QUERY; import retrofit2.http.Query; import retrofit2.http.QueryMap; import retrofit2.http.QueryName; @@ -1009,7 +1009,7 @@ Call method() { @Test public void query() { class Example { - @QUERY_METHOD("/foo/bar/") // + @QUERY("/foo/bar/") // Call method(@Body RequestBody body) { return null; } diff --git a/retrofit/src/main/java/retrofit2/http/QUERY_METHOD.java b/retrofit/src/main/extra/retrofit2/http/QUERY.java similarity index 97% rename from retrofit/src/main/java/retrofit2/http/QUERY_METHOD.java rename to retrofit/src/main/extra/retrofit2/http/QUERY.java index 257f146a28..55fbb9522c 100644 --- a/retrofit/src/main/java/retrofit2/http/QUERY_METHOD.java +++ b/retrofit/src/main/extra/retrofit2/http/QUERY.java @@ -27,7 +27,7 @@ @Documented @Target(METHOD) @Retention(RUNTIME) -public @interface QUERY_METHOD { +public @interface QUERY { /** * A relative or absolute path, or full URL of the endpoint. This value is optional if the first * parameter of the method is annotated with {@link Url @Url}. diff --git a/retrofit/src/main/java/retrofit2/RequestFactory.java b/retrofit/src/main/java/retrofit2/RequestFactory.java index 20e13daca9..eeae27de39 100644 --- a/retrofit/src/main/java/retrofit2/RequestFactory.java +++ b/retrofit/src/main/java/retrofit2/RequestFactory.java @@ -56,7 +56,7 @@ import retrofit2.http.Part; import retrofit2.http.PartMap; import retrofit2.http.Path; -import retrofit2.http.QUERY_METHOD; +import retrofit2.http.QUERY; import retrofit2.http.Query; import retrofit2.http.QueryMap; import retrofit2.http.QueryName; @@ -245,8 +245,8 @@ private void parseMethodAnnotation(Annotation annotation) { parseHttpMethodAndPath("PUT", ((PUT) annotation).value(), true); } else if (annotation instanceof OPTIONS) { parseHttpMethodAndPath("OPTIONS", ((OPTIONS) annotation).value(), false); - } else if (annotation instanceof QUERY_METHOD) { - parseHttpMethodAndPath("QUERY", ((QUERY_METHOD) annotation).value(), true); + } else if (annotation instanceof QUERY) { + parseHttpMethodAndPath("QUERY", ((QUERY) annotation).value(), true); } else if (annotation instanceof HTTP) { HTTP http = (HTTP) annotation; parseHttpMethodAndPath(http.method(), http.path(), http.hasBody()); diff --git a/retrofit/src/main/java/retrofit2/Retrofit.java b/retrofit/src/main/java/retrofit2/Retrofit.java index af6b64f261..323a5404ad 100644 --- a/retrofit/src/main/java/retrofit2/Retrofit.java +++ b/retrofit/src/main/java/retrofit2/Retrofit.java @@ -112,7 +112,7 @@ public final class Retrofit { * the request type. The built-in methods are {@link retrofit2.http.GET GET}, {@link * retrofit2.http.PUT PUT}, {@link retrofit2.http.POST POST}, {@link retrofit2.http.PATCH PATCH}, * {@link retrofit2.http.HEAD HEAD}, {@link retrofit2.http.DELETE DELETE}, - * {@link retrofit2.http.OPTIONS OPTIONS}, and {@link retrofit2.http.QUERY_METHOD QUERY_METHOD}. + * {@link retrofit2.http.OPTIONS OPTIONS}, and {@link retrofit2.http.QUERY QUERY}. * You can use a custom HTTP method with {@link HTTP @HTTP}. For * a dynamic URL, omit the path on the annotation and annotate the first parameter with {@link * Url @Url}. From fad2263916169ca2e4dafed16e9a2dfd79e38c29 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 12 Dec 2025 10:46:34 +0800 Subject: [PATCH 3/3] Fix circular dependencies --- retrofit/build.gradle | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/retrofit/build.gradle b/retrofit/build.gradle index 43ce931f58..280fea55c8 100644 --- a/retrofit/build.gradle +++ b/retrofit/build.gradle @@ -2,25 +2,17 @@ apply plugin: 'java-library' apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'com.vanniktech.maven.publish' -def addExtraSourceSet(String name) { - def sourceSet = sourceSets.create(name) { - java.srcDir("src/main/$name") - } - - // Propagate dependencies to be visible to this source set. - configurations.getByName("${name}Implementation").extendsFrom(configurations.getByName('implementation')) - configurations.getByName("${name}Api").extendsFrom(configurations.getByName('api')) - configurations.getByName("${name}CompileOnly").extendsFrom(configurations.getByName('compileOnly')) - - return sourceSet -} - def addMultiReleaseSourceSet(int version) { - def name = "java${version}" - def sourceSet = addExtraSourceSet(name) + def sourceSet = sourceSets.create("java$version") + sourceSet.java.srcDir("src/main/java$version") + + // Propagate dependencies to be visible to this version's source set. + configurations.getByName("java${version}Implementation").extendsFrom(configurations.getByName('implementation')) + configurations.getByName("java${version}Api").extendsFrom(configurations.getByName('api')) + configurations.getByName("java${version}CompileOnly").extendsFrom(configurations.getByName('compileOnly')) // Allow types in the main source set to be visible to this version's source set. - dependencies.add("${name}Implementation", sourceSets.getByName("main").output) + dependencies.add("java${version}Implementation", sourceSets.getByName("main").output) tasks.named("compileJava${version}Java", JavaCompile) { javaCompiler = javaToolchains.compilerFor { @@ -39,17 +31,22 @@ def addMultiReleaseSourceSet(int version) { addMultiReleaseSourceSet(14) addMultiReleaseSourceSet(16) -def extra = addExtraSourceSet('extra') +def extra = sourceSets.create('extra') { + java.srcDir('src/main/extra') +} dependencies { api libs.okhttp.client + api extra.output - compileOnly extra.output compileOnly libs.android compileOnly libs.kotlinx.coroutines compileOnly libs.animalSnifferAnnotations compileOnly libs.findBugsAnnotations + + // Can't extend extraCompileOnly from compileOnly due to the circular dependencies. + extraCompileOnly libs.okhttp.client } javadoc {