Skip to content

Commit

Permalink
Merge branch 'master' into return-kt-result-type
Browse files Browse the repository at this point in the history
  • Loading branch information
Goooler committed Mar 1, 2024
2 parents 1a65812 + 0a99604 commit b002131
Show file tree
Hide file tree
Showing 37 changed files with 887 additions and 218 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/gradle-wrapper.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/wrapper-validation-action@v1
- uses: gradle/wrapper-validation-action@v2
31 changes: 21 additions & 10 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
[versions]
kotlin = "1.9.22"
okhttp = "3.14.9"
protobuf = "3.25.2"
protobuf = "3.25.3"
robovm = "2.3.14"
kotlinx-serialization = "1.6.2"
kotlinx-serialization = "1.6.3"
autoService = "1.1.1"
incap = "1.0.0"
jackson = "2.16.1"

[libraries]
androidPlugin = { module = "com.android.tools.build:gradle", version = "8.2.2" }
androidPlugin = { module = "com.android.tools.build:gradle", version = "8.3.0" }
robovmPlugin = { module = "com.mobidevelop.robovm:robovm-gradle-plugin", version.ref = "robovm" }
dokkaPlugin = "org.jetbrains.dokka:dokka-gradle-plugin:1.9.10"
gradleMavenPublishPlugin = "com.vanniktech:gradle-maven-publish-plugin:0.27.0"
Expand All @@ -41,14 +44,20 @@ protobufPlugin = "com.google.protobuf:protobuf-gradle-plugin:0.9.4"
protobuf = { module = "com.google.protobuf:protobuf-java", version.ref = "protobuf" }
protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }

kotlinCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.7.3" }
incap-runtime = { module = "net.ltgt.gradle.incap:incap", version.ref = "incap" }
incap-processor = { module = "net.ltgt.gradle.incap:incap-processor", version.ref = "incap" }

autoService-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "autoService" }
autoService-compiler = { module = "com.google.auto.service:auto-service", version.ref = "autoService" }

kotlinCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.8.0" }
kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinx-serialization" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
kotlinx-serialization-proto = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinx-serialization" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
junit = { module = "junit:junit", version = "4.13.2" }
truth = "com.google.truth:truth:1.3.0"
truth = "com.google.truth:truth:1.4.1"
guava = { module = "com.google.guava:guava", version = "33.0.0-jre" }
android = { module = "com.google.android:android", version = "4.1.1.4" }
findBugsAnnotations = { module = "com.google.code.findbugs:jsr305", version = "3.0.2" }
Expand All @@ -57,17 +66,19 @@ rxjava = { module = "io.reactivex:rxjava", version = "1.3.8" }
rxjava2 = { module = "io.reactivex.rxjava2:rxjava", version = "2.2.21" }
rxjava3 = { module = "io.reactivex.rxjava3:rxjava", version = "3.1.8" }
reactiveStreams = { module = "org.reactivestreams:reactive-streams", version = "1.0.4" }
scalaLibrary = { module = "org.scala-lang:scala-library", version = "2.13.12" }
scalaLibrary = { module = "org.scala-lang:scala-library", version = "2.13.13" }
gson = { module = "com.google.code.gson:gson", version = "2.10.1" }
jacksonDatabind = { module = "com.fasterxml.jackson.core:jackson-databind", version = "2.16.1" }
jacksonDatabind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jacksonDataformatCbor = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-cbor", version.ref = "jackson" }
jaxbApi = { module = "javax.xml.bind:jaxb-api", version = "2.3.1" }
jaxbImpl = { module = "org.glassfish.jaxb:jaxb-runtime", version = "4.0.4" }
jaxb3Api = { module = "jakarta.xml.bind:jakarta.xml.bind-api", version = "3.0.1" }
jaxb3Impl = { module = "com.sun.xml.bind:jaxb-impl", version = "3.0.2" }
moshi = { module = "com.squareup.moshi:moshi", version = "1.15.0" }
moshi = { module = "com.squareup.moshi:moshi", version = "1.15.1" }
simpleXml = { module = "org.simpleframework:simple-xml", version = "2.7.1" }
wireRuntime = { module = "com.squareup.wire:wire-runtime", version = "2.2.0" }
jsoup = { module = "org.jsoup:jsoup", version = "1.17.2" }
robovm = { module = "com.mobidevelop.robovm:robovm-rt", version.ref = "robovm" }
googleJavaFormat = "com.google.googlejavaformat:google-java-format:1.19.2"
ktlint = "com.pinterest.ktlint:ktlint-cli:1.1.1"
googleJavaFormat = "com.google.googlejavaformat:google-java-format:1.20.0"
ktlint = "com.pinterest.ktlint:ktlint-cli:1.2.1"
compileTesting = "com.google.testing.compile:compile-testing:0.21.0"
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
1 change: 1 addition & 0 deletions retrofit-converters/jackson/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dependencies {
testImplementation libs.junit
testImplementation libs.truth
testImplementation libs.mockwebserver
testImplementation libs.jacksonDataformatCbor
}

jar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.fasterxml.jackson.databind.ObjectWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Converter;
Expand All @@ -35,22 +36,33 @@
* instance} last to allow the other converters a chance to see their types.
*/
public final class JacksonConverterFactory extends Converter.Factory {
private static final MediaType DEFAULT_MEDIA_TYPE =
MediaType.get("application/json; charset=UTF-8");

/** Create an instance using a default {@link ObjectMapper} instance for conversion. */
public static JacksonConverterFactory create() {
return create(new ObjectMapper());
return new JacksonConverterFactory(new ObjectMapper(), DEFAULT_MEDIA_TYPE);
}

/** Create an instance using {@code mapper} for conversion. */
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static JacksonConverterFactory create(ObjectMapper mapper) {
return create(mapper, DEFAULT_MEDIA_TYPE);
}

/** Create an instance using {@code mapper} and {@code mediaType} for conversion. */
@SuppressWarnings("ConstantConditions") // Guarding public API nullability.
public static JacksonConverterFactory create(ObjectMapper mapper, MediaType mediaType) {
if (mapper == null) throw new NullPointerException("mapper == null");
return new JacksonConverterFactory(mapper);
if (mediaType == null) throw new NullPointerException("mediaType == null");
return new JacksonConverterFactory(mapper, mediaType);
}

private final ObjectMapper mapper;
private final MediaType mediaType;

private JacksonConverterFactory(ObjectMapper mapper) {
private JacksonConverterFactory(ObjectMapper mapper, MediaType mediaType) {
this.mapper = mapper;
this.mediaType = mediaType;
}

@Override
Expand All @@ -69,6 +81,6 @@ public Converter<?, RequestBody> requestBodyConverter(
Retrofit retrofit) {
JavaType javaType = mapper.getTypeFactory().constructType(type);
ObjectWriter writer = mapper.writerFor(javaType);
return new JacksonRequestBodyConverter<>(writer);
return new JacksonRequestBodyConverter<>(writer, mediaType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@
import retrofit2.Converter;

final class JacksonRequestBodyConverter<T> implements Converter<T, RequestBody> {
private static final MediaType MEDIA_TYPE = MediaType.get("application/json; charset=UTF-8");

private final ObjectWriter adapter;
private final MediaType mediaType;

JacksonRequestBodyConverter(ObjectWriter adapter) {
JacksonRequestBodyConverter(ObjectWriter adapter, MediaType mediaType) {
this.adapter = adapter;
this.mediaType = mediaType;
}

@Override
public RequestBody convert(T value) throws IOException {
byte[] bytes = adapter.writeValueAsBytes(value);
return RequestBody.create(MEDIA_TYPE, bytes);
return RequestBody.create(mediaType, bytes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class JacksonResponseBodyConverter<T> implements Converter<ResponseBody, T
@Override
public T convert(ResponseBody value) throws IOException {
try {
return adapter.readValue(value.charStream());
return adapter.readValue(value.byteStream());
} finally {
value.close();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2024 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.converter.jackson;

import static com.google.common.truth.Truth.assertThat;

import com.fasterxml.jackson.dataformat.cbor.databind.CBORMapper;
import java.io.IOException;
import okhttp3.MediaType;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import okio.Buffer;
import okio.ByteString;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.http.Body;
import retrofit2.http.POST;

public class JacksonCborConverterFactoryTest {
static class IntWrapper {
public int value;

public IntWrapper(int v) {
value = v;
}

protected IntWrapper() {}
}

interface Service {
@POST("/")
Call<IntWrapper> post(@Body IntWrapper person);
}

@Rule public final MockWebServer server = new MockWebServer();

private Service service;

@Before
public void setUp() {
Retrofit retrofit =
new Retrofit.Builder()
.baseUrl(server.url("/"))
.addConverterFactory(
JacksonConverterFactory.create(new CBORMapper(), MediaType.get("application/cbor")))
.build();
service = retrofit.create(Service.class);
}

@Test
public void post() throws IOException, InterruptedException {
server.enqueue(
new MockResponse()
.setBody(new Buffer().write(ByteString.decodeHex("bf6576616c7565182aff"))));

Call<IntWrapper> call = service.post(new IntWrapper(12));
Response<IntWrapper> response = call.execute();
assertThat(response.body().value).isEqualTo(42);

RecordedRequest request = server.takeRequest();
assertThat(request.getBody().readByteString())
.isEqualTo(ByteString.decodeHex("bf6576616c75650cff"));
assertThat(request.getHeader("Content-Type")).isEqualTo("application/cbor");
}
}
48 changes: 48 additions & 0 deletions retrofit-response-type-keeper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Response Type Keeper

Generates keep rules for types mentioned in generic parameter positions of Retrofit service methods.

## Problem

Given a service method like
```java
@GET("users/{id}")
Call<User> getUser(
@Path("id") String id);
```

If you execute this request and do not actually use the returned `User` instance, R8 will remove it
and replace the return type as `Call<?>`. This fails Retrofit's runtime validation since a wildcard
is not a valid type to pass to a converter. Note: this removal only occurs if the Retrofit's service
method definition is the only reference to `User`.

## Solution

This module contains an annotation processor which looks at each Retrofit method and generates
explicit `-keep` rules for the types mentioned.

Add it to Gradle Java projects with
```groovy
annotationProcessor 'com.squareup.retrofit2:response-type-keeper:<version>'
```
Or Gradle Kotlin projects with
```groovy
kapt 'com.squareup.retrofit2:response-type-keeper:<version>'
```

For other build systems, the `com.squareup.retrofit2:response-type-keeper` needs added to the Java
compiler `-processor` classpath.

For the example above, the annotation processor's generated file would contain
```
-keep com.example.User
```

This works for nested generics, such as `Call<ApiResponse<User>>`, which would produce:
```
-keep com.example.ApiResponse
-keep com.example.User
```

It also works on Kotlin `suspend` functions which turn into a type like
`Continuation<? extends User>` in the Java bytecode.
15 changes: 15 additions & 0 deletions retrofit-response-type-keeper/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.jetbrains.kotlin.kapt'
apply plugin: 'com.vanniktech.maven.publish'

dependencies {
compileOnly libs.autoService.annotations
compileOnly libs.incap.runtime
kapt libs.autoService.compiler
kapt libs.incap.processor

testImplementation libs.junit
testImplementation libs.compileTesting
testImplementation libs.truth
testImplementation projects.retrofit
}
3 changes: 3 additions & 0 deletions retrofit-response-type-keeper/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_ARTIFACT_ID=response-type-keeper
POM_NAME=Response Type Keeper
POM_DESCRIPTION=Annotation processor to generate R8 keep rules for types mentioned in generics.
Loading

0 comments on commit b002131

Please sign in to comment.