Support CBOR and Protobuf with Kotlin Serialization

This commit introduces support for CBOR and Protobuf using Kotlin
serialization. Support comes in the form of Encoder/Decoder as well
as HttpMessageConverters. Seperate abstract base classes supply support
for binary and string (de)serialization.

The exising JSON codecs and message converters have been migrated to
use the new base classes.

Closes gh-27628
This commit is contained in:
Iain Henderson
2021-10-31 15:05:21 -04:00
committed by Arjen Poutsma
parent b6c2e8de23
commit b8243e6f84
40 changed files with 2398 additions and 415 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -107,7 +107,7 @@ public class CancelWithoutDemandCodecTests {
Flux<DataBuffer> flux = encoder.encode(Mono.just(msg),
this.bufferFactory, ResolvableType.forClass(Msg.class),
new MimeType("application", "x-protobuf"), Collections.emptyMap());
MediaType.APPLICATION_PROTOBUF, Collections.emptyMap());
BaseSubscriber<DataBuffer> subscriber = new ZeroDemandSubscriber();
flux.subscribe(subscriber); // Assume sync execution (e.g. encoding with Flux.just)..

View File

@@ -33,7 +33,6 @@ import org.springframework.core.testfixture.codec.AbstractDecoderTests;
import org.springframework.http.MediaType;
import org.springframework.protobuf.Msg;
import org.springframework.protobuf.SecondMsg;
import org.springframework.util.MimeType;
import static java.util.Collections.emptyMap;
import static org.assertj.core.api.Assertions.assertThat;
@@ -48,8 +47,6 @@ import static org.springframework.core.io.buffer.DataBufferUtils.release;
*/
public class ProtobufDecoderTests extends AbstractDecoderTests<ProtobufDecoder> {
private final static MimeType PROTOBUF_MIME_TYPE = new MimeType("application", "x-protobuf");
private final SecondMsg secondMsg = SecondMsg.newBuilder().setBlah(123).build();
private final Msg testMsg1 = Msg.newBuilder().setFoo("Foo").setBlah(secondMsg).build();
@@ -72,10 +69,10 @@ public class ProtobufDecoderTests extends AbstractDecoderTests<ProtobufDecoder>
@Override
public void canDecode() {
assertThat(this.decoder.canDecode(forClass(Msg.class), null)).isTrue();
assertThat(this.decoder.canDecode(forClass(Msg.class), PROTOBUF_MIME_TYPE)).isTrue();
assertThat(this.decoder.canDecode(forClass(Msg.class), MediaType.APPLICATION_PROTOBUF)).isTrue();
assertThat(this.decoder.canDecode(forClass(Msg.class), MediaType.APPLICATION_OCTET_STREAM)).isTrue();
assertThat(this.decoder.canDecode(forClass(Msg.class), MediaType.APPLICATION_JSON)).isFalse();
assertThat(this.decoder.canDecode(forClass(Object.class), PROTOBUF_MIME_TYPE)).isFalse();
assertThat(this.decoder.canDecode(forClass(Object.class), MediaType.APPLICATION_PROTOBUF)).isFalse();
}
@Test

View File

@@ -55,6 +55,8 @@ import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.codec.ResourceHttpMessageReader;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.ServerSentEventHttpMessageReader;
import org.springframework.http.codec.cbor.KotlinSerializationCborDecoder;
import org.springframework.http.codec.cbor.KotlinSerializationCborEncoder;
import org.springframework.http.codec.json.Jackson2CodecSupport;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
@@ -64,6 +66,8 @@ import org.springframework.http.codec.json.KotlinSerializationJsonDecoder;
import org.springframework.http.codec.json.KotlinSerializationJsonEncoder;
import org.springframework.http.codec.multipart.MultipartHttpMessageWriter;
import org.springframework.http.codec.multipart.PartEventHttpMessageWriter;
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufDecoder;
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufEncoder;
import org.springframework.http.codec.protobuf.ProtobufDecoder;
import org.springframework.http.codec.protobuf.ProtobufHttpMessageWriter;
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
@@ -88,7 +92,7 @@ public class ClientCodecConfigurerTests {
@Test
public void defaultReaders() {
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
assertThat(readers.size()).isEqualTo(15);
assertThat(readers.size()).isEqualTo(17);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class);
@@ -99,7 +103,9 @@ public class ClientCodecConfigurerTests {
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ProtobufDecoder.class);
// SPR-16804
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(FormHttpMessageReader.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationCborDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationJsonDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationProtobufDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jackson2JsonDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jackson2SmileDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jaxb2XmlDecoder.class);
@@ -110,7 +116,7 @@ public class ClientCodecConfigurerTests {
@Test
public void defaultWriters() {
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
assertThat(writers.size()).isEqualTo(15);
assertThat(writers.size()).isEqualTo(17);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class);
@@ -121,7 +127,9 @@ public class ClientCodecConfigurerTests {
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class);
assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(MultipartHttpMessageWriter.class);
assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartEventHttpMessageWriter.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationCborEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationJsonEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationProtobufEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2JsonEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2SmileEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jaxb2XmlEncoder.class);
@@ -176,7 +184,7 @@ public class ClientCodecConfigurerTests {
int size = 99;
this.configurer.defaultCodecs().maxInMemorySize(size);
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
assertThat(readers.size()).isEqualTo(15);
assertThat(readers.size()).isEqualTo(17);
assertThat(((ByteArrayDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((ByteBufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((DataBufferDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
@@ -187,7 +195,9 @@ public class ClientCodecConfigurerTests {
assertThat(((ProtobufDecoder) getNextDecoder(readers)).getMaxMessageSize()).isEqualTo(size);
assertThat(((FormHttpMessageReader) nextReader(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((KotlinSerializationCborDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((KotlinSerializationJsonDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((KotlinSerializationProtobufDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((Jackson2JsonDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((Jackson2SmileDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((Jaxb2XmlDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
@@ -235,7 +245,7 @@ public class ClientCodecConfigurerTests {
writers = findCodec(this.configurer.getWriters(), MultipartHttpMessageWriter.class).getPartWriters();
assertThat(sseDecoder).isNotSameAs(jackson2Decoder);
assertThat(writers).hasSize(13);
assertThat(writers).hasSize(15);
}
@Test // gh-24194
@@ -245,7 +255,7 @@ public class ClientCodecConfigurerTests {
List<HttpMessageWriter<?>> writers =
findCodec(clone.getWriters(), MultipartHttpMessageWriter.class).getPartWriters();
assertThat(writers).hasSize(13);
assertThat(writers).hasSize(15);
}
@Test
@@ -259,7 +269,7 @@ public class ClientCodecConfigurerTests {
List<HttpMessageWriter<?>> writers =
findCodec(clone.getWriters(), MultipartHttpMessageWriter.class).getPartWriters();
assertThat(writers).hasSize(13);
assertThat(writers).hasSize(15);
}
private Decoder<?> getNextDecoder(List<HttpMessageReader<?>> readers) {

View File

@@ -50,12 +50,16 @@ import org.springframework.http.codec.ResourceHttpMessageReader;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.ServerSentEventHttpMessageReader;
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
import org.springframework.http.codec.cbor.KotlinSerializationCborDecoder;
import org.springframework.http.codec.cbor.KotlinSerializationCborEncoder;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.json.Jackson2SmileDecoder;
import org.springframework.http.codec.json.Jackson2SmileEncoder;
import org.springframework.http.codec.json.KotlinSerializationJsonDecoder;
import org.springframework.http.codec.json.KotlinSerializationJsonEncoder;
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufDecoder;
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufEncoder;
import org.springframework.http.codec.protobuf.ProtobufDecoder;
import org.springframework.http.codec.protobuf.ProtobufEncoder;
import org.springframework.http.codec.protobuf.ProtobufHttpMessageWriter;
@@ -83,7 +87,7 @@ class CodecConfigurerTests {
@Test
void defaultReaders() {
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
assertThat(readers.size()).isEqualTo(14);
assertThat(readers.size()).isEqualTo(16);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class);
@@ -93,7 +97,9 @@ class CodecConfigurerTests {
assertStringDecoder(getNextDecoder(readers), true);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ProtobufDecoder.class);
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(FormHttpMessageReader.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationCborDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationJsonDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationProtobufDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jackson2JsonDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jackson2SmileDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jaxb2XmlDecoder.class);
@@ -103,7 +109,7 @@ class CodecConfigurerTests {
@Test
void defaultWriters() {
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
assertThat(writers.size()).isEqualTo(13);
assertThat(writers.size()).isEqualTo(15);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class);
@@ -112,7 +118,9 @@ class CodecConfigurerTests {
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ResourceHttpMessageWriter.class);
assertStringEncoder(getNextEncoder(writers), true);
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationCborEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationJsonEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationProtobufEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2JsonEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2SmileEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jaxb2XmlEncoder.class);
@@ -141,7 +149,7 @@ class CodecConfigurerTests {
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
assertThat(readers.size()).isEqualTo(18);
assertThat(readers.size()).isEqualTo(20);
assertThat(getNextDecoder(readers)).isSameAs(customDecoder1);
assertThat(readers.get(this.index.getAndIncrement())).isSameAs(customReader1);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class);
@@ -155,7 +163,9 @@ class CodecConfigurerTests {
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(FormHttpMessageReader.class);
assertThat(getNextDecoder(readers)).isSameAs(customDecoder2);
assertThat(readers.get(this.index.getAndIncrement())).isSameAs(customReader2);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationCborDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationJsonDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationProtobufDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jackson2JsonDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jackson2SmileDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jaxb2XmlDecoder.class);
@@ -184,7 +194,7 @@ class CodecConfigurerTests {
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
assertThat(writers.size()).isEqualTo(17);
assertThat(writers.size()).isEqualTo(19);
assertThat(getNextEncoder(writers)).isSameAs(customEncoder1);
assertThat(writers.get(this.index.getAndIncrement())).isSameAs(customWriter1);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class);
@@ -197,7 +207,9 @@ class CodecConfigurerTests {
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class);
assertThat(getNextEncoder(writers)).isSameAs(customEncoder2);
assertThat(writers.get(this.index.getAndIncrement())).isSameAs(customWriter2);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationCborEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationJsonEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationProtobufEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2JsonEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2SmileEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jaxb2XmlEncoder.class);

View File

@@ -54,6 +54,8 @@ import org.springframework.http.codec.ResourceHttpMessageReader;
import org.springframework.http.codec.ResourceHttpMessageWriter;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
import org.springframework.http.codec.cbor.KotlinSerializationCborDecoder;
import org.springframework.http.codec.cbor.KotlinSerializationCborEncoder;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.codec.json.Jackson2SmileDecoder;
@@ -64,6 +66,8 @@ import org.springframework.http.codec.multipart.DefaultPartHttpMessageReader;
import org.springframework.http.codec.multipart.MultipartHttpMessageReader;
import org.springframework.http.codec.multipart.PartEventHttpMessageReader;
import org.springframework.http.codec.multipart.PartHttpMessageWriter;
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufDecoder;
import org.springframework.http.codec.protobuf.KotlinSerializationProtobufEncoder;
import org.springframework.http.codec.protobuf.ProtobufDecoder;
import org.springframework.http.codec.protobuf.ProtobufHttpMessageWriter;
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
@@ -88,7 +92,7 @@ public class ServerCodecConfigurerTests {
@Test
public void defaultReaders() {
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
assertThat(readers.size()).isEqualTo(17);
assertThat(readers.size()).isEqualTo(19);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteArrayDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(ByteBufferDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(DataBufferDecoder.class);
@@ -101,7 +105,9 @@ public class ServerCodecConfigurerTests {
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(DefaultPartHttpMessageReader.class);
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(MultipartHttpMessageReader.class);
assertThat(readers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartEventHttpMessageReader.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationCborDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationJsonDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(KotlinSerializationProtobufDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jackson2JsonDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jackson2SmileDecoder.class);
assertThat(getNextDecoder(readers).getClass()).isEqualTo(Jaxb2XmlDecoder.class);
@@ -111,7 +117,7 @@ public class ServerCodecConfigurerTests {
@Test
public void defaultWriters() {
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
assertThat(writers.size()).isEqualTo(15);
assertThat(writers.size()).isEqualTo(17);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteArrayEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(ByteBufferEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(DataBufferEncoder.class);
@@ -121,7 +127,9 @@ public class ServerCodecConfigurerTests {
assertStringEncoder(getNextEncoder(writers), true);
assertThat(writers.get(index.getAndIncrement()).getClass()).isEqualTo(ProtobufHttpMessageWriter.class);
assertThat(writers.get(this.index.getAndIncrement()).getClass()).isEqualTo(PartHttpMessageWriter.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationCborEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationJsonEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(KotlinSerializationProtobufEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2JsonEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jackson2SmileEncoder.class);
assertThat(getNextEncoder(writers).getClass()).isEqualTo(Jaxb2XmlEncoder.class);
@@ -168,7 +176,9 @@ public class ServerCodecConfigurerTests {
assertThat((reader).getMaxInMemorySize()).isEqualTo(size);
assertThat(((PartEventHttpMessageReader) nextReader(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((KotlinSerializationCborDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((KotlinSerializationJsonDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((KotlinSerializationProtobufDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((Jackson2JsonDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((Jackson2SmileDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);
assertThat(((Jaxb2XmlDecoder) getNextDecoder(readers)).getMaxInMemorySize()).isEqualTo(size);

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* 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
*
* https://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 org.springframework.http.codec.cbor
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.serializer
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import reactor.test.StepVerifier.FirstStep
import org.springframework.core.Ordered
import org.springframework.core.ResolvableType
import org.springframework.core.io.buffer.DataBuffer
import org.springframework.core.testfixture.codec.AbstractDecoderTests
import org.springframework.http.MediaType
/**
* Tests for the JSON decoding using kotlinx.serialization.
*
* @author Sebastien Deleuze
* @author Iain Henderson
*/
@ExperimentalSerializationApi
class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializationCborDecoder>(KotlinSerializationCborDecoder()) {
@Suppress("UsePropertyAccessSyntax", "DEPRECATION")
@Test
override fun canDecode() {
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), null)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(String::class.java), null)).isFalse()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse()
}
@Test
override fun decode() {
val output = decoder.decode(Mono.empty(), ResolvableType.forClass(Pojo::class.java), null, emptyMap())
StepVerifier
.create(output)
.expectError(UnsupportedOperationException::class.java)
}
@Test
override fun decodeToMono() {
val pojo1 = Pojo("f1", "b1")
val input = Flux.concat(
byteBuffer(pojo1)
)
val elementType = ResolvableType.forClass(Pojo::class.java)
testDecodeToMonoAll(input, elementType, { step: FirstStep<Any> ->
step
.expectNext(pojo1)
.expectComplete()
.verify()
}, null, null)
}
private fun byteBuffer(value: Any): Mono<DataBuffer> {
return Mono.defer {
val bytes = Cbor.Default.encodeToByteArray(serializer(Pojo::class.java), value)
val buffer = bufferFactory.allocateBuffer(bytes.size)
buffer.write(bytes)
Mono.just(buffer)
}
}
}
@Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* 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
*
* https://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 org.springframework.http.codec.cbor
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.encodeToByteArray
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier.FirstStep
import org.springframework.core.Ordered
import org.springframework.core.ResolvableType
import org.springframework.core.io.buffer.DataBuffer
import org.springframework.core.io.buffer.DataBufferUtils
import org.springframework.core.testfixture.codec.AbstractEncoderTests
import org.springframework.http.MediaType
import org.springframework.http.codec.ServerSentEvent
/**
* Tests for the JSON encoding using kotlinx.serialization.
*
* @author Sebastien Deleuze
* @author Iain Henderson
*/
@Suppress("UsePropertyAccessSyntax")
@ExperimentalSerializationApi
class KotlinSerializationCborEncoderTests : AbstractEncoderTests<KotlinSerializationCborEncoder>(KotlinSerializationCborEncoder()) {
@Test
override fun canEncode() {
val pojoType = ResolvableType.forClass(Pojo::class.java)
Assertions.assertThat(encoder.canEncode(pojoType, MediaType.APPLICATION_CBOR)).isTrue()
Assertions.assertThat(encoder.canEncode(pojoType, null)).isTrue()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
}
@Test
override fun encode() {
val pojo1 = Pojo("foo", "bar")
val pojo2 = Pojo("foofoo", "barbar")
val pojo3 = Pojo("foofoofoo", "barbarbar")
val input = Flux.just(
pojo1,
pojo2,
pojo3
)
val pojoBytes = Cbor.Default.encodeToByteArray(arrayOf(pojo1, pojo2, pojo3))
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer?> ->
step
.consumeNextWith(expectBytes(pojoBytes)
.andThen { dataBuffer: DataBuffer? -> DataBufferUtils.release(dataBuffer) })
.verifyComplete()
}
}
@Test
fun encodeMono() {
val pojo = Pojo("foo", "bar")
val input = Mono.just(pojo)
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer?> ->
step
.consumeNextWith(expectBytes(Cbor.Default.encodeToByteArray(pojo))
.andThen { dataBuffer: DataBuffer? -> DataBufferUtils.release(dataBuffer) })
.verifyComplete()
}
}
@Test
fun canNotEncode() {
Assertions.assertThat(encoder.canEncode(ResolvableType.forClass(String::class.java), null)).isFalse()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
val sseType = ResolvableType.forClass(ServerSentEvent::class.java)
Assertions.assertThat(encoder.canEncode(sseType, MediaType.APPLICATION_CBOR)).isFalse()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse()
}
@Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* 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
*
* https://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 org.springframework.http.codec.protobuf
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.serializer
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.springframework.core.Ordered
import org.springframework.core.ResolvableType
import org.springframework.core.io.buffer.DataBuffer
import org.springframework.core.testfixture.codec.AbstractDecoderTests
import org.springframework.http.MediaType
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import reactor.test.StepVerifier.FirstStep
/**
* Tests for the JSON decoding using kotlinx.serialization.
*
* @author Sebastien Deleuze
* @author Iain Henderson
*/
@ExperimentalSerializationApi
class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializationProtobufDecoder>(KotlinSerializationProtobufDecoder()) {
@Suppress("UsePropertyAccessSyntax")
@Test
override fun canDecode() {
for (mimeType in listOf(MediaType.APPLICATION_PROTOBUF, MediaType.APPLICATION_OCTET_STREAM, MediaType("application", "vnd.google.protobuf"))) {
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java),mimeType)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), mimeType)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), mimeType)).isFalse()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), mimeType)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), mimeType)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), mimeType)).isFalse()
}
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), null)).isTrue()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(String::class.java), null)).isFalse()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
Assertions.assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
}
@Test
override fun decode() {
val output = decoder.decode(Mono.empty(), ResolvableType.forClass(Pojo::class.java), null, emptyMap())
StepVerifier
.create(output)
.expectError(UnsupportedOperationException::class.java)
.verify();
}
@Test
override fun decodeToMono() {
val pojo1 = Pojo("f1", "b1")
val pojo2 = Pojo("f2", "b2")
val input = Flux.concat(
byteBuffer(pojo1),
byteBuffer(pojo2)
)
val elementType = ResolvableType.forClass(Pojo::class.java)
testDecodeToMonoAll(input, elementType, { step: FirstStep<Any> ->
step
.expectNext(pojo2)
.expectComplete()
.verify()
}, null, null)
}
private fun byteBuffer(value: Any): Mono<DataBuffer> {
return Mono.defer {
val bytes = ProtoBuf.Default.encodeToByteArray(serializer(Pojo::class.java), value)
val buffer = bufferFactory.allocateBuffer(bytes.size)
buffer.write(bytes)
Mono.just(buffer)
}
}
}
@Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* 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
*
* https://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 org.springframework.http.codec.protobuf
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.springframework.core.Ordered
import org.springframework.core.ResolvableType
import org.springframework.core.io.buffer.DataBuffer
import org.springframework.core.io.buffer.DataBufferUtils
import org.springframework.core.testfixture.codec.AbstractEncoderTests
import org.springframework.http.MediaType
import org.springframework.http.codec.ServerSentEvent
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier.FirstStep
/**
* Tests for the JSON encoding using kotlinx.serialization.
*
* @author Sebastien Deleuze
* @author Iain Henderson
*/
@Suppress("UsePropertyAccessSyntax")
@ExperimentalSerializationApi
class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSerializationProtobufEncoder>(KotlinSerializationProtobufEncoder()) {
private val mediaTypes = listOf(
MediaType.APPLICATION_PROTOBUF,
MediaType.APPLICATION_OCTET_STREAM,
MediaType("application", "vnd.google.protobuf")
)
@Test
override fun canEncode() {
val pojoType = ResolvableType.forClass(Pojo::class.java)
Assertions.assertThat(encoder.canEncode(pojoType, null)).isTrue()
for (mimeType in ProtobufCodecSupport.MIME_TYPES) {
val mediaType = MediaType(mimeType)
Assertions.assertThat(encoder.canEncode(pojoType, mediaType)).isTrue()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), mimeType)).isTrue()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), mimeType)).isFalse()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), mimeType)).isTrue()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), mimeType)).isTrue()
}
Assertions.assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
}
@Test
override fun encode() {
val pojo1 = Pojo("foo", "bar")
val pojo2 = Pojo("foofoo", "barbar")
val pojo3 = Pojo("foofoofoo", "barbarbar")
val input = Flux.just(
pojo1,
pojo2,
pojo3
)
val pojoBytes = ProtoBuf.Default.encodeToByteArray(arrayOf(pojo1, pojo2, pojo3))
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer?> ->
step
.consumeNextWith(expectBytes(pojoBytes)
.andThen { dataBuffer: DataBuffer? -> DataBufferUtils.release(dataBuffer) })
.verifyComplete()
}
}
@Test
fun encodeMono() {
val pojo = Pojo("foo", "bar")
val input = Mono.just(pojo)
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer?> ->
step
.consumeNextWith(expectBytes(ProtoBuf.Default.encodeToByteArray(pojo))
.andThen { dataBuffer: DataBuffer? -> DataBufferUtils.release(dataBuffer) })
.verifyComplete()
}
}
@Test
fun canNotEncode() {
Assertions.assertThat(encoder.canEncode(ResolvableType.forClass(String::class.java), null)).isFalse()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
val sseType = ResolvableType.forClass(ServerSentEvent::class.java)
for (mediaType in mediaTypes) {
Assertions.assertThat(encoder.canEncode(sseType, mediaType)).isFalse()
Assertions.assertThat(encoder.canEncode(ResolvableType.forClass(Ordered::class.java), mediaType)).isFalse()
}
}
@Serializable
data class Pojo(val foo: String, val bar: String, val pojo: Pojo? = null)
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* 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
*
* https://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 org.springframework.http.converter.cbor
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.nio.charset.StandardCharsets
import kotlin.reflect.javaType
import kotlin.reflect.typeOf
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.encodeToByteArray
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.jupiter.api.Test
import org.springframework.core.Ordered
import org.springframework.http.MediaType
import org.springframework.http.MockHttpInputMessage
import org.springframework.http.MockHttpOutputMessage
import org.springframework.http.converter.HttpMessageNotReadableException
/**
* Tests for the CBOR conversion using kotlinx.serialization.
*
* @author Andreas Ahlenstorf
* @author Sebastien Deleuze
* @author Iain Henderson
*/
@Suppress("UsePropertyAccessSyntax")
@ExperimentalSerializationApi
class KotlinSerializationCborHttpMessageConverterTests {
private val converter = KotlinSerializationCborHttpMessageConverter()
private val serializableBean = SerializableBean(
bytes = byteArrayOf(0x1, 0x2),
array = arrayOf("Foo", "Bar"),
number = 42,
string = "Foo",
bool = true,
fraction = 42f
)
private val serializableBeanArray = arrayOf(serializableBean)
private val serializableBeanArrayBody = Cbor.Default.encodeToByteArray(serializableBeanArray)
@Test
fun canReadCbor() {
assertThat(converter.canRead(SerializableBean::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canRead(String::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(NotSerializableBean::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canRead(Map::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canRead(typeTokenOf<Map<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(List::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canRead(typeTokenOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(Set::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canRead(typeTokenOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(typeTokenOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canRead(typeTokenOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canRead(typeTokenOf<List<Ordered>>(), List::class.java, MediaType.APPLICATION_CBOR)).isFalse()
}
@Test
fun canWriteCbor() {
assertThat(converter.canWrite(SerializableBean::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canWrite(String::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(NotSerializableBean::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canWrite(Map::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canWrite(typeTokenOf<Map<String, SerializableBean>>(), Map::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(List::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canWrite(typeTokenOf<List<SerializableBean>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(Set::class.java, MediaType.APPLICATION_CBOR)).isFalse()
assertThat(converter.canWrite(typeTokenOf<Set<SerializableBean>>(), Set::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(typeTokenOf<ArrayList<Int>>(), List::class.java, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canWrite(typeTokenOf<Ordered>(), Ordered::class.java, MediaType.APPLICATION_CBOR)).isFalse()
}
@Test
fun readObject() {
val serializableBeanBody = Cbor.Default.encodeToByteArray(serializableBean)
val inputMessage = MockHttpInputMessage(serializableBeanBody)
inputMessage.headers.contentType = MediaType.APPLICATION_CBOR
val result = converter.read(SerializableBean::class.java, inputMessage) as SerializableBean
assertThat(result.bytes).containsExactly(*serializableBean.bytes)
assertThat(result.array).containsExactly(*serializableBean.array)
assertThat(result.number).isEqualTo(serializableBean.number)
assertThat(result.string).isEqualTo(serializableBean.string)
assertThat(result.bool).isTrue()
assertThat(result.fraction).isEqualTo(serializableBean.fraction)
}
@Test
@Suppress("UNCHECKED_CAST")
fun readArrayOfObjects() {
val inputMessage = MockHttpInputMessage(serializableBeanArrayBody)
inputMessage.headers.contentType = MediaType.APPLICATION_CBOR
val result = converter.read(Array<SerializableBean>::class.java, inputMessage) as Array<SerializableBean>
assertThat(result).hasSize(1)
assertThat(result[0].bytes).containsExactly(*serializableBean.bytes)
assertThat(result[0].array).containsExactly(*serializableBean.array)
assertThat(result[0].number).isEqualTo(serializableBean.number)
assertThat(result[0].string).isEqualTo(serializableBean.string)
assertThat(result[0].bool).isTrue()
assertThat(result[0].fraction).isEqualTo(serializableBean.fraction)
}
@Test
@Suppress("UNCHECKED_CAST")
@ExperimentalStdlibApi
fun readGenericCollection() {
val inputMessage = MockHttpInputMessage(serializableBeanArrayBody)
inputMessage.headers.contentType = MediaType.APPLICATION_CBOR
val result = converter.read(typeOf<List<SerializableBean>>().javaType, null, inputMessage)
as List<SerializableBean>
assertThat(result).hasSize(1)
assertThat(result[0].bytes).containsExactly(*serializableBean.bytes)
assertThat(result[0].array).containsExactly(*serializableBean.array)
assertThat(result[0].number).isEqualTo(serializableBean.number)
assertThat(result[0].string).isEqualTo(serializableBean.string)
assertThat(result[0].bool).isTrue()
assertThat(result[0].fraction).isEqualTo(serializableBean.fraction)
}
@Test
fun readFailsOnInvalidCbor() {
val body = """
this is an invalid JSON document and definitely NOT CBOR
""".trimIndent()
val inputMessage = MockHttpInputMessage(body.toByteArray(StandardCharsets.UTF_8))
inputMessage.headers.contentType = MediaType.APPLICATION_CBOR
assertThatExceptionOfType(HttpMessageNotReadableException::class.java).isThrownBy {
converter.read(SerializableBean::class.java, inputMessage)
}
}
@Test
fun writeObject() {
val outputMessage = MockHttpOutputMessage()
this.converter.write(serializableBean, null, outputMessage)
assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/cbor"))
assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue()
}
@Test
fun writeObjectWithNullableProperty() {
val outputMessage = MockHttpOutputMessage()
val serializableBean = SerializableBean(byteArrayOf(0x1, 0x2), arrayOf("Foo", "Bar"), 42, null, true, 42.0f)
this.converter.write(serializableBean, null, outputMessage)
assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/cbor"))
assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue()
}
@Test
fun writeArrayOfObjects() {
val outputMessage = MockHttpOutputMessage()
this.converter.write(serializableBeanArray, null, outputMessage)
assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/cbor"))
assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue()
}
@Test
@ExperimentalStdlibApi
fun writeGenericCollection() {
val outputMessage = MockHttpOutputMessage()
this.converter.write(listOf(serializableBean), typeOf<List<SerializableBean>>().javaType, null, outputMessage)
assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/cbor"))
assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue()
}
@Serializable
@Suppress("ArrayInDataClass")
data class SerializableBean(
val bytes: ByteArray,
val array: Array<String>,
val number: Int,
val string: String?,
val bool: Boolean,
val fraction: Float,
val serializableBean: SerializableBean? = null
)
data class NotSerializableBean(val string: String)
open class TypeBase<T>
inline fun <reified T> typeTokenOf(): Type {
val base = object : TypeBase<T>() {}
val superType = base::class.java.genericSuperclass!!
return (superType as ParameterizedType).actualTypeArguments.first()!!
}
}

View File

@@ -16,21 +16,23 @@
package org.springframework.http.converter.json
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.nio.charset.StandardCharsets
import kotlinx.serialization.Serializable
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.jupiter.api.Test
import kotlin.reflect.javaType
import kotlin.reflect.typeOf
import org.springframework.core.Ordered
import org.springframework.core.ResolvableType
import org.springframework.http.MediaType
import org.springframework.http.MockHttpInputMessage
import org.springframework.http.MockHttpOutputMessage
import org.springframework.http.converter.HttpMessageNotReadableException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.nio.charset.StandardCharsets
import kotlin.reflect.javaType
import kotlin.reflect.typeOf
/**
* Tests for the JSON conversion using kotlinx.serialization.

View File

@@ -0,0 +1,247 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* 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
*
* https://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 org.springframework.http.converter.protobuf
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.protobuf.ProtoBuf
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.jupiter.api.Test
import org.springframework.core.Ordered
import org.springframework.http.MediaType
import org.springframework.http.MockHttpInputMessage
import org.springframework.http.MockHttpOutputMessage
import org.springframework.http.converter.HttpMessageNotReadableException
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.nio.charset.StandardCharsets
import kotlin.reflect.javaType
import kotlin.reflect.typeOf
/**
* Tests for the Protocol Buffer conversion using kotlinx.serialization.
*
* @author Andreas Ahlenstorf
* @author Sebastien Deleuze
* @author Iain Henderson
*/
@Suppress("UsePropertyAccessSyntax")
@ExperimentalSerializationApi
class KotlinSerializationProtobufHttpMessageConverterTests {
private val converter = KotlinSerializationProtobufHttpMessageConverter()
private val mediaTypes = listOf(
MediaType.APPLICATION_PROTOBUF,
MediaType.APPLICATION_OCTET_STREAM,
MediaType("application", "vnd.google.protobuf")
)
private val serializableBean = SerializableBean(
bytes = byteArrayOf(0x1, 0x2),
array = arrayOf("Foo", "Bar"),
number = 42,
string = "Foo",
bool = true,
fraction = 42f
)
private val serializableBeanArray = arrayOf(serializableBean)
private val serializableBeanArrayBody = ProtoBuf.Default.encodeToByteArray(serializableBeanArray)
@Test
fun canReadProtobuf() {
for (mimeType in mediaTypes) {
assertThat(converter.canRead(SerializableBean::class.java, mimeType)).isTrue()
assertThat(converter.canRead(String::class.java, mimeType)).isTrue()
assertThat(converter.canRead(NotSerializableBean::class.java, mimeType)).isFalse()
assertThat(converter.canRead(Map::class.java, mimeType)).isFalse()
assertThat(converter.canRead(typeTokenOf<Map<String, SerializableBean>>(), Map::class.java, mimeType)).isTrue()
assertThat(converter.canRead(List::class.java, mimeType)).isFalse()
assertThat(converter.canRead(typeTokenOf<List<SerializableBean>>(), List::class.java, mimeType)).isTrue()
assertThat(converter.canRead(Set::class.java, mimeType)).isFalse()
assertThat(converter.canRead(typeTokenOf<Set<SerializableBean>>(), Set::class.java, mimeType)).isTrue()
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, mimeType)).isTrue()
assertThat(converter.canRead(typeTokenOf<ArrayList<Int>>(), List::class.java, mimeType)).isTrue()
assertThat(converter.canRead(typeTokenOf<Ordered>(), Ordered::class.java, mimeType)).isFalse()
assertThat(converter.canRead(typeTokenOf<List<Ordered>>(), List::class.java, mimeType)).isFalse()
}
assertThat(converter.canRead(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canRead(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
}
@Test
fun canWriteProtobuf() {
for (mimeType in mediaTypes) {
assertThat(converter.canWrite(SerializableBean::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(String::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(NotSerializableBean::class.java, mimeType)).isFalse()
assertThat(converter.canWrite(Map::class.java, mimeType)).isFalse()
assertThat(converter.canWrite(typeTokenOf<Map<String, SerializableBean>>(), Map::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(List::class.java, mimeType)).isFalse()
assertThat(converter.canWrite(typeTokenOf<List<SerializableBean>>(), List::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(Set::class.java, mimeType)).isFalse()
assertThat(converter.canWrite(typeTokenOf<Set<SerializableBean>>(), Set::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(typeTokenOf<ArrayList<Int>>(), List::class.java, mimeType)).isTrue()
assertThat(converter.canWrite(typeTokenOf<Ordered>(), Ordered::class.java, mimeType)).isFalse()
}
assertThat(converter.canWrite(SerializableBean::class.java, MediaType.APPLICATION_JSON)).isFalse()
assertThat(converter.canWrite(typeTokenOf<List<Int>>(), List::class.java, MediaType.APPLICATION_JSON)).isFalse()
}
@Test
fun readObject() {
val serializableBeanBody = ProtoBuf.Default.encodeToByteArray(serializableBean)
for (mimeType in mediaTypes) {
val inputMessage = MockHttpInputMessage(serializableBeanBody)
inputMessage.headers.contentType = mimeType
val result = converter.read(SerializableBean::class.java, inputMessage) as SerializableBean
assertThat(result.bytes).containsExactly(*serializableBean.bytes)
assertThat(result.array).containsExactly(*serializableBean.array)
assertThat(result.number).isEqualTo(serializableBean.number)
assertThat(result.string).isEqualTo(serializableBean.string)
assertThat(result.bool).isTrue()
assertThat(result.fraction).isEqualTo(serializableBean.fraction)
}
}
@Test
@Suppress("UNCHECKED_CAST")
fun readArrayOfObjects() {
for (mimeType in mediaTypes) {
val inputMessage = MockHttpInputMessage(serializableBeanArrayBody)
inputMessage.headers.contentType = mimeType
val result = converter.read(Array<SerializableBean>::class.java, inputMessage) as Array<SerializableBean>
assertThat(result).hasSize(1)
assertThat(result[0].bytes).containsExactly(*serializableBean.bytes)
assertThat(result[0].array).containsExactly(*serializableBean.array)
assertThat(result[0].number).isEqualTo(serializableBean.number)
assertThat(result[0].string).isEqualTo(serializableBean.string)
assertThat(result[0].bool).isTrue()
assertThat(result[0].fraction).isEqualTo(serializableBean.fraction)
}
}
@Test
@Suppress("UNCHECKED_CAST")
@ExperimentalStdlibApi
fun readGenericCollection() {
for (mimeType in mediaTypes) {
val inputMessage = MockHttpInputMessage(serializableBeanArrayBody)
inputMessage.headers.contentType = mimeType
val result = converter.read(typeOf<List<SerializableBean>>().javaType, null, inputMessage)
as List<SerializableBean>
assertThat(result).hasSize(1)
assertThat(result[0].bytes).containsExactly(*serializableBean.bytes)
assertThat(result[0].array).containsExactly(*serializableBean.array)
assertThat(result[0].number).isEqualTo(serializableBean.number)
assertThat(result[0].string).isEqualTo(serializableBean.string)
assertThat(result[0].bool).isTrue()
assertThat(result[0].fraction).isEqualTo(serializableBean.fraction)
}
}
@Test
fun readFailsOnInvalidProtobuf() {
val body = """
this is an invalid JSON document and definitely NOT a Protocol Buffer
""".trimIndent()
for (mimeType in mediaTypes) {
val inputMessage = MockHttpInputMessage(body.toByteArray(StandardCharsets.UTF_8))
inputMessage.headers.contentType = mimeType
assertThatExceptionOfType(HttpMessageNotReadableException::class.java).isThrownBy {
converter.read(SerializableBean::class.java, inputMessage)
}
}
}
@Test
fun writeObject() {
val outputMessage = MockHttpOutputMessage()
this.converter.write(serializableBean, null, outputMessage)
assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/x-protobuf"))
assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue()
}
@Test
fun writeObjectWithNullableProperty() {
val outputMessage = MockHttpOutputMessage()
val serializableBean = SerializableBean(byteArrayOf(0x1, 0x2), arrayOf("Foo", "Bar"), 42, null, true, 42.0f)
this.converter.write(serializableBean, null, outputMessage)
assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/x-protobuf"))
assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue()
}
@Test
fun writeArrayOfObjects() {
val outputMessage = MockHttpOutputMessage()
this.converter.write(serializableBeanArray, null, outputMessage)
assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/x-protobuf"))
assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue()
}
@Test
@ExperimentalStdlibApi
fun writeGenericCollection() {
val outputMessage = MockHttpOutputMessage()
this.converter.write(listOf(serializableBean), typeOf<List<SerializableBean>>().javaType, null, outputMessage)
assertThat(outputMessage.headers).containsEntry("Content-Type", listOf("application/x-protobuf"))
assertThat(outputMessage.bodyAsBytes.isNotEmpty()).isTrue()
}
@Serializable
@Suppress("ArrayInDataClass")
data class SerializableBean(
val bytes: ByteArray,
val array: Array<String>,
val number: Int,
val string: String?,
val bool: Boolean,
val fraction: Float,
val serializableBean: SerializableBean? = null
)
data class NotSerializableBean(val string: String)
open class TypeBase<T>
inline fun <reified T> typeTokenOf(): Type {
val base = object : TypeBase<T>() {}
val superType = base::class.java.genericSuperclass!!
return (superType as ParameterizedType).actualTypeArguments.first()!!
}
}