Browse Source

Prevent Kotlin Serialization codecs side effects

This commit updates Kotlin serialization codecs to perform
an additional check invoking
KotlinDetector#hasSerializableAnnotation to decide if the
related type should be processed or not.

The goal is to prevent in the default arrangement conflicts
between general purpose codecs like Jackson and
Kotlin serialization when both are used.

New constructors allowing to specify a custom predicate
are also introduced.

See gh-35761
pull/34993/merge
Sébastien Deleuze 1 month ago
parent
commit
19dd488dd7
  1. 25
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationBinaryDecoder.java
  2. 25
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationBinaryEncoder.java
  3. 25
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringDecoder.java
  4. 26
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringEncoder.java
  5. 39
      spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationSupport.java
  6. 47
      spring-web/src/main/java/org/springframework/http/codec/cbor/KotlinSerializationCborDecoder.java
  7. 46
      spring-web/src/main/java/org/springframework/http/codec/cbor/KotlinSerializationCborEncoder.java
  8. 55
      spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java
  9. 52
      spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonEncoder.java
  10. 46
      spring-web/src/main/java/org/springframework/http/codec/protobuf/KotlinSerializationProtobufDecoder.java
  11. 45
      spring-web/src/main/java/org/springframework/http/codec/protobuf/KotlinSerializationProtobufEncoder.java
  12. 29
      spring-web/src/test/kotlin/org/springframework/http/codec/cbor/KotlinSerializationCborDecoderTests.kt
  13. 22
      spring-web/src/test/kotlin/org/springframework/http/codec/cbor/KotlinSerializationCborEncoderTests.kt
  14. 2
      spring-web/src/test/kotlin/org/springframework/http/codec/json/CustomKotlinSerializationJsonDecoderTests.kt
  15. 2
      spring-web/src/test/kotlin/org/springframework/http/codec/json/CustomKotlinSerializationJsonEncoderTests.kt
  16. 38
      spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt
  17. 58
      spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonEncoderTests.kt
  18. 6
      spring-web/src/test/kotlin/org/springframework/http/codec/protobuf/KotlinSerializationProtobufDecoderTests.kt
  19. 30
      spring-web/src/test/kotlin/org/springframework/http/codec/protobuf/KotlinSerializationProtobufEncoderTests.kt

25
spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationBinaryDecoder.java

@ -18,6 +18,7 @@ package org.springframework.http.codec; @@ -18,6 +18,7 @@ package org.springframework.http.codec;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import kotlinx.serialization.BinaryFormat;
import kotlinx.serialization.KSerializer;
@ -37,9 +38,11 @@ import org.springframework.util.MimeType; @@ -37,9 +38,11 @@ import org.springframework.util.MimeType;
* Abstract base class for {@link Decoder} implementations that defer to Kotlin
* {@linkplain BinaryFormat binary serializers}.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only decodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
* since it allows combined usage with other general purpose decoders without conflicts.
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
* to customize this behavior.
*
* @author Sebastien Deleuze
* @author Iain Henderson
@ -54,10 +57,26 @@ public abstract class KotlinSerializationBinaryDecoder<T extends BinaryFormat> e @@ -54,10 +57,26 @@ public abstract class KotlinSerializationBinaryDecoder<T extends BinaryFormat> e
private final ByteArrayDecoder byteArrayDecoder = new ByteArrayDecoder();
/**
* Creates a new instance with the given format and supported mime types
* which only decodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or
* generics level.
*/
public KotlinSerializationBinaryDecoder(T format, MimeType... supportedMimeTypes) {
super(format, supportedMimeTypes);
}
/**
* Creates a new instance with the given format and supported mime types
* which only decodes types for which the specified predicate returns
* {@code true}.
* @since 7.0
*/
public KotlinSerializationBinaryDecoder(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
super(format, typePredicate, supportedMimeTypes);
}
/**
* Configure a limit on the number of bytes that can be buffered whenever
* the input stream needs to be aggregated. This can be a result of

25
spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationBinaryEncoder.java

@ -18,6 +18,7 @@ package org.springframework.http.codec; @@ -18,6 +18,7 @@ package org.springframework.http.codec;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import kotlinx.serialization.BinaryFormat;
import kotlinx.serialization.KSerializer;
@ -38,9 +39,11 @@ import org.springframework.util.MimeType; @@ -38,9 +39,11 @@ import org.springframework.util.MimeType;
* Abstract base class for {@link Encoder} implementations that defer to Kotlin
* {@linkplain BinaryFormat binary serializers}.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only encodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
* since it allows combined usage with other general purpose encoders without conflicts.
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
* to customize this behavior.
*
* @author Sebastien Deleuze
* @author Iain Henderson
@ -54,10 +57,26 @@ public abstract class KotlinSerializationBinaryEncoder<T extends BinaryFormat> e @@ -54,10 +57,26 @@ public abstract class KotlinSerializationBinaryEncoder<T extends BinaryFormat> e
// ByteArraySequence encoding needed for now, see https://github.com/Kotlin/kotlinx.serialization/issues/204 for more details
private final ByteArrayEncoder byteArrayEncoder = new ByteArrayEncoder();
/**
* Creates a new instance with the given format and supported mime types
* which only encodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or
* generics level.
*/
protected KotlinSerializationBinaryEncoder(T format, MimeType... supportedMimeTypes) {
super(format, supportedMimeTypes);
}
/**
* Creates a new instance with the given format and supported mime types
* which only encodes types for which the specified predicate returns
* {@code true}.
* @since 7.0
*/
protected KotlinSerializationBinaryEncoder(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
super(format, typePredicate, supportedMimeTypes);
}
@Override
public boolean canEncode(ResolvableType elementType, @Nullable MimeType mimeType) {
return canSerialize(elementType, mimeType);

25
spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringDecoder.java

@ -18,6 +18,7 @@ package org.springframework.http.codec; @@ -18,6 +18,7 @@ package org.springframework.http.codec;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import kotlinx.serialization.KSerializer;
import kotlinx.serialization.StringFormat;
@ -38,9 +39,11 @@ import org.springframework.util.MimeType; @@ -38,9 +39,11 @@ import org.springframework.util.MimeType;
* Abstract base class for {@link Decoder} implementations that defer to Kotlin
* {@linkplain StringFormat string serializers}.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only decodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
* since it allows combined usage with other general purpose decoders without conflicts.
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
* to customize this behavior.
*
* @author Sebastien Deleuze
* @author Iain Henderson
@ -55,10 +58,26 @@ public abstract class KotlinSerializationStringDecoder<T extends StringFormat> e @@ -55,10 +58,26 @@ public abstract class KotlinSerializationStringDecoder<T extends StringFormat> e
private final StringDecoder stringDecoder = StringDecoder.allMimeTypes(StringDecoder.DEFAULT_DELIMITERS, false);
/**
* Creates a new instance with the given format and supported mime types
* which only decodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or
* generics level.
*/
public KotlinSerializationStringDecoder(T format, MimeType... supportedMimeTypes) {
super(format, supportedMimeTypes);
}
/**
* Creates a new instance with the given format and supported mime types
* which only decodes types for which the specified predicate returns
* {@code true}.
* @since 7.0
*/
public KotlinSerializationStringDecoder(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
super(format, typePredicate, supportedMimeTypes);
}
/**
* Configure a limit on the number of bytes that can be buffered whenever
* the input stream needs to be aggregated. This can be a result of

26
spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationStringEncoder.java

@ -22,6 +22,7 @@ import java.util.HashSet; @@ -22,6 +22,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import kotlinx.serialization.KSerializer;
import kotlinx.serialization.StringFormat;
@ -43,9 +44,11 @@ import org.springframework.util.MimeType; @@ -43,9 +44,11 @@ import org.springframework.util.MimeType;
* Abstract base class for {@link Encoder} implementations that defer to Kotlin
* {@linkplain StringFormat string serializers}.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only encodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
* since it allows combined usage with other general purpose encoders without conflicts.
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
* to customize this behavior.
*
* @author Sebastien Deleuze
* @author Iain Henderson
@ -65,10 +68,27 @@ public abstract class KotlinSerializationStringEncoder<T extends StringFormat> e @@ -65,10 +68,27 @@ public abstract class KotlinSerializationStringEncoder<T extends StringFormat> e
private final CharSequenceEncoder charSequenceEncoder = CharSequenceEncoder.allMimeTypes();
private final Set<MimeType> streamingMediaTypes = new HashSet<>();
/**
* Creates a new instance with the given format and supported mime types
* which only encodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or
* generics level.
*/
protected KotlinSerializationStringEncoder(T format, MimeType... supportedMimeTypes) {
super(format, supportedMimeTypes);
}
/**
* Creates a new instance with the given format and supported mime types
* which only encodes types for which the specified predicate returns
* {@code true}.
* @since 7.0
*/
protected KotlinSerializationStringEncoder(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
super(format, typePredicate, supportedMimeTypes);
}
/**
* Set streaming {@link MediaType MediaTypes}.
* @param streamingMediaTypes streaming {@link MediaType MediaTypes}

39
spring-web/src/main/java/org/springframework/http/codec/KotlinSerializationSupport.java

@ -21,6 +21,7 @@ import java.lang.reflect.Type; @@ -21,6 +21,7 @@ import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import kotlin.reflect.KFunction;
import kotlin.reflect.KType;
@ -42,9 +43,11 @@ import org.springframework.util.MimeType; @@ -42,9 +43,11 @@ import org.springframework.util.MimeType;
* Base class providing support methods for encoding and decoding with Kotlin
* serialization.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only handles types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics level
* since it allows combined usage with other general purpose decoders without conflicts.
* Alternative constructors with a {@code Predicate<ResolvableType>} parameter can be used
* to customize this behavior.
*
* @author Sebastien Deleuze
* @author Iain Henderson
@ -63,12 +66,29 @@ public abstract class KotlinSerializationSupport<T extends SerialFormat> { @@ -63,12 +66,29 @@ public abstract class KotlinSerializationSupport<T extends SerialFormat> {
private final List<MimeType> supportedMimeTypes;
private final Predicate<ResolvableType> typePredicate;
/**
* Creates a new instance of this support class with the given format
* and supported mime types.
* Creates a new instance with the given format and supported mime types
* which only handle types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or
* generics level.
*/
protected KotlinSerializationSupport(T format, MimeType... supportedMimeTypes) {
this.format = format;
this.typePredicate = KotlinDetector::hasSerializableAnnotation;
this.supportedMimeTypes = Arrays.asList(supportedMimeTypes);
}
/**
* Creates a new instance with the given format and supported mime types
* which only encode types for which the specified predicate returns
* {@code true}.
* @since 7.0
*/
protected KotlinSerializationSupport(T format, Predicate<ResolvableType> typePredicate, MimeType... supportedMimeTypes) {
this.format = format;
this.typePredicate = typePredicate;
this.supportedMimeTypes = Arrays.asList(supportedMimeTypes);
}
@ -94,15 +114,10 @@ public abstract class KotlinSerializationSupport<T extends SerialFormat> { @@ -94,15 +114,10 @@ public abstract class KotlinSerializationSupport<T extends SerialFormat> {
* @return {@code true} if {@code type} can be serialized; false otherwise
*/
protected final boolean canSerialize(ResolvableType type, @Nullable MimeType mimeType) {
KSerializer<Object> serializer = serializer(type);
if (serializer == null) {
if (!this.typePredicate.test(type) || ResolvableType.NONE.equals(type)) {
return false;
}
else {
return (supports(mimeType) && !String.class.isAssignableFrom(type.toClass()) &&
!ServerSentEvent.class.isAssignableFrom(type.toClass()));
}
return serializer(type) != null && supports(mimeType);
}
private boolean supports(@Nullable MimeType mimeType) {

47
spring-web/src/main/java/org/springframework/http/codec/cbor/KotlinSerializationCborDecoder.java

@ -16,8 +16,11 @@ @@ -16,8 +16,11 @@
package org.springframework.http.codec.cbor;
import java.util.function.Predicate;
import kotlinx.serialization.cbor.Cbor;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.codec.KotlinSerializationBinaryDecoder;
@ -26,24 +29,62 @@ import org.springframework.http.codec.KotlinSerializationBinaryDecoder; @@ -26,24 +29,62 @@ import org.springframework.http.codec.KotlinSerializationBinaryDecoder;
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
* It supports {@code application/cbor}.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only decodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics
* level since it allows combined usage with other general purpose CBOR decoders
* like {@link JacksonCborDecoder} without conflicts.
*
* <p>Alternative constructors with a {@code Predicate<ResolvableType>}
* parameter can be used to customize this behavior. For example,
* {@code new KotlinSerializationCborDecoder(type -> true)} will decode all types
* supported by Kotlin Serialization, including unannotated Kotlin enumerations,
* numbers, characters, booleans and strings.
*
* <p>Decoding streams is not supported yet, see
* <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a>
* related issue.
*
* @author Iain Henderson
* @author Sebastien Deleuze
* @since 6.0
* @see KotlinSerializationCborEncoder
*/
public class KotlinSerializationCborDecoder extends KotlinSerializationBinaryDecoder<Cbor> {
/**
* Construct a new decoder using {@link Cbor.Default} instance which
* only decodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationCborDecoder() {
this(Cbor.Default);
}
/**
* Construct a new decoder using {@link Cbor.Default} instance which
* only decodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationCborDecoder(Predicate<ResolvableType> typePredicate) {
this(Cbor.Default, typePredicate);
}
/**
* Construct a new decoder using the provided {@link Cbor} instance which
* only decodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationCborDecoder(Cbor cbor) {
super(cbor, MediaType.APPLICATION_CBOR);
}
/**
* Construct a new decoder using the provided {@link Cbor} instance which
* only decodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationCborDecoder(Cbor cbor, Predicate<ResolvableType> typePredicate) {
super(cbor, typePredicate, MediaType.APPLICATION_CBOR);
}
}

46
spring-web/src/main/java/org/springframework/http/codec/cbor/KotlinSerializationCborEncoder.java

@ -16,8 +16,11 @@ @@ -16,8 +16,11 @@
package org.springframework.http.codec.cbor;
import java.util.function.Predicate;
import kotlinx.serialization.cbor.Cbor;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.codec.KotlinSerializationBinaryEncoder;
@ -26,21 +29,58 @@ import org.springframework.http.codec.KotlinSerializationBinaryEncoder; @@ -26,21 +29,58 @@ import org.springframework.http.codec.KotlinSerializationBinaryEncoder;
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
* It supports {@code application/cbor}.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only encodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics
* level since it allows combined usage with other general purpose JSON encoders
* like {@link JacksonCborEncoder} without conflicts.
*
* <p>Alternative constructors with a {@code Predicate<ResolvableType>}
* parameter can be used to customize this behavior. For example,
* {@code new KotlinSerializationCborEncoder(type -> true)} will encode all types
* supported by Kotlin Serialization, including unannotated Kotlin enumerations,
* numbers, characters, booleans and strings.
*
* @author Iain Henderson
* @author Sebastien Deleuze
* @since 6.0
* @see KotlinSerializationCborDecoder
*/
public class KotlinSerializationCborEncoder extends KotlinSerializationBinaryEncoder<Cbor> {
/**
* Construct a new encoder using {@link Cbor.Default} instance which
* only encodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationCborEncoder() {
this(Cbor.Default);
}
/**
* Construct a new encoder using {@link Cbor.Default} instance which
* only encodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationCborEncoder(Predicate<ResolvableType> typePredicate) {
this(Cbor.Default, typePredicate);
}
/**
* Construct a new encoder using the provided {@link Cbor} instance which
* only encodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationCborEncoder(Cbor cbor) {
super(cbor, MediaType.APPLICATION_CBOR);
}
/**
* Construct a new encoder using the provided {@link Cbor} instance which
* only encodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationCborEncoder(Cbor cbor, Predicate<ResolvableType> typePredicate) {
super(cbor, typePredicate, MediaType.APPLICATION_CBOR);
}
}

55
spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonDecoder.java

@ -16,10 +16,14 @@ @@ -16,10 +16,14 @@
package org.springframework.http.codec.json;
import java.util.function.Predicate;
import kotlinx.serialization.json.Json;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.codec.KotlinSerializationStringDecoder;
import org.springframework.util.MimeType;
/**
* Decode a byte stream into JSON and convert to Object's with
@ -27,9 +31,16 @@ import org.springframework.http.codec.KotlinSerializationStringDecoder; @@ -27,9 +31,16 @@ import org.springframework.http.codec.KotlinSerializationStringDecoder;
* It supports {@code application/json} and {@code application/*+json} with
* various character sets, {@code UTF-8} being the default.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only decodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics
* level since it allows combined usage with other general purpose JSON decoders
* like {@link JacksonJsonDecoder} without conflicts.
*
* <p>Alternative constructors with a {@code Predicate<ResolvableType>}
* parameter can be used to customize this behavior. For example,
* {@code new KotlinSerializationJsonDecoder(type -> true)} will decode all types
* supported by Kotlin Serialization, including unannotated Kotlin enumerations,
* numbers, characters, booleans and strings.
*
* <p>Decoding streams is not supported yet, see
* <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a>
@ -38,16 +49,50 @@ import org.springframework.http.codec.KotlinSerializationStringDecoder; @@ -38,16 +49,50 @@ import org.springframework.http.codec.KotlinSerializationStringDecoder;
* @author Sebastien Deleuze
* @author Iain Henderson
* @since 5.3
* @see KotlinSerializationJsonEncoder
*/
public class KotlinSerializationJsonDecoder extends KotlinSerializationStringDecoder<Json> {
private static final MimeType[] DEFAULT_JSON_MIME_TYPES = new MimeType[] {
MediaType.APPLICATION_JSON,
new MediaType("application", "*+json"),
MediaType.APPLICATION_NDJSON
};
/**
* Construct a new decoder using {@link Json.Default} instance which
* only decodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationJsonDecoder() {
this(Json.Default);
}
/**
* Construct a new decoder using {@link Json.Default} instance which
* only decodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationJsonDecoder(Predicate<ResolvableType> typePredicate) {
this(Json.Default, typePredicate);
}
/**
* Construct a new decoder using the provided {@link Json} instance which
* only decodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationJsonDecoder(Json json) {
super(json, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"),
MediaType.APPLICATION_NDJSON);
super(json, DEFAULT_JSON_MIME_TYPES);
}
/**
* Construct a new decoder using the provided {@link Json} instance which
* only decodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationJsonDecoder(Json json, Predicate<ResolvableType> typePredicate) {
super(json, typePredicate, DEFAULT_JSON_MIME_TYPES);
}
}

52
spring-web/src/main/java/org/springframework/http/codec/json/KotlinSerializationJsonEncoder.java

@ -18,6 +18,7 @@ package org.springframework.http.codec.json; @@ -18,6 +18,7 @@ package org.springframework.http.codec.json;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import kotlinx.serialization.json.Json;
import org.jspecify.annotations.Nullable;
@ -38,23 +39,64 @@ import org.springframework.util.MimeType; @@ -38,23 +39,64 @@ import org.springframework.util.MimeType;
* It supports {@code application/json}, {@code application/x-ndjson} and {@code application/*+json} with
* various character sets, {@code UTF-8} being the default.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only encodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics
* level.
*
* <p>Alternative constructors with a {@code Predicate<ResolvableType>}
* parameter can be used to customize this behavior. For example,
* {@code new KotlinSerializationJsonEncoder(type -> true)} will encode all types
* supported by Kotlin Serialization, including unannotated Kotlin enumerations,
* numbers, characters, booleans and strings.
*
* @author Sebastien Deleuze
* @author Iain Henderson
* @since 5.3
* @see KotlinSerializationJsonDecoder
*/
public class KotlinSerializationJsonEncoder extends KotlinSerializationStringEncoder<Json> {
private static final MimeType[] DEFAULT_JSON_MIME_TYPES = new MimeType[] {
MediaType.APPLICATION_JSON,
new MediaType("application", "*+json"),
MediaType.APPLICATION_NDJSON
};
/**
* Construct a new encoder using {@link Json.Default} instance which
* only encodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationJsonEncoder() {
this(Json.Default);
}
/**
* Construct a new encoder using {@link Json.Default} instance which
* only encodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationJsonEncoder(Predicate<ResolvableType> typePredicate) {
this(Json.Default, typePredicate);
}
/**
* Construct a new encoder using the provided {@link Json} instance which
* only encodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationJsonEncoder(Json json) {
super(json, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"),
MediaType.APPLICATION_NDJSON);
super(json, DEFAULT_JSON_MIME_TYPES);
setStreamingMediaTypes(List.of(MediaType.APPLICATION_NDJSON));
}
/**
* Construct a new encoder using the provided {@link Json} instance which
* only encodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationJsonEncoder(Json json, Predicate<ResolvableType> typePredicate) {
super(json, typePredicate, DEFAULT_JSON_MIME_TYPES);
setStreamingMediaTypes(List.of(MediaType.APPLICATION_NDJSON));
}

46
spring-web/src/main/java/org/springframework/http/codec/protobuf/KotlinSerializationProtobufDecoder.java

@ -16,8 +16,11 @@ @@ -16,8 +16,11 @@
package org.springframework.http.codec.protobuf;
import java.util.function.Predicate;
import kotlinx.serialization.protobuf.ProtoBuf;
import org.springframework.core.ResolvableType;
import org.springframework.http.codec.KotlinSerializationBinaryDecoder;
/**
@ -25,24 +28,61 @@ import org.springframework.http.codec.KotlinSerializationBinaryDecoder; @@ -25,24 +28,61 @@ import org.springframework.http.codec.KotlinSerializationBinaryDecoder;
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
* It supports {@code application/x-protobuf}, {@code application/octet-stream}, and {@code application/vnd.google.protobuf}.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only decodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics
* level.
*
* <p>Alternative constructors with a {@code Predicate<ResolvableType>}
* parameter can be used to customize this behavior. For example,
* {@code new KotlinSerializationProtobufDecoder(type -> true)} will decode all types
* supported by Kotlin Serialization, including unannotated Kotlin enumerations,
* numbers, characters, booleans and strings.
*
* <p>Decoding streams is not supported yet, see
* <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a>
* related issue.
*
* @author Iain Henderson
* @author Sebastien Deleuze
* @since 6.0
* @see KotlinSerializationProtobufEncoder
*/
public class KotlinSerializationProtobufDecoder extends KotlinSerializationBinaryDecoder<ProtoBuf> {
/**
* Construct a new decoder using {@link ProtoBuf.Default} instance which
* only decodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationProtobufDecoder() {
this(ProtoBuf.Default);
}
/**
* Construct a new decoder using {@link ProtoBuf.Default} instance which
* only decodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationProtobufDecoder(Predicate<ResolvableType> typePredicate) {
this(ProtoBuf.Default, typePredicate);
}
/**
* Construct a new decoder using the provided {@link ProtoBuf} instance which
* only decodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationProtobufDecoder(ProtoBuf protobuf) {
super(protobuf, ProtobufCodecSupport.MIME_TYPES);
}
/**
* Construct a new decoder using the provided {@link ProtoBuf} instance which
* only decodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationProtobufDecoder(ProtoBuf protobuf, Predicate<ResolvableType> typePredicate) {
super(protobuf, typePredicate, ProtobufCodecSupport.MIME_TYPES);
}
}

45
spring-web/src/main/java/org/springframework/http/codec/protobuf/KotlinSerializationProtobufEncoder.java

@ -16,8 +16,11 @@ @@ -16,8 +16,11 @@
package org.springframework.http.codec.protobuf;
import java.util.function.Predicate;
import kotlinx.serialization.protobuf.ProtoBuf;
import org.springframework.core.ResolvableType;
import org.springframework.http.codec.KotlinSerializationBinaryEncoder;
/**
@ -25,24 +28,60 @@ import org.springframework.http.codec.KotlinSerializationBinaryEncoder; @@ -25,24 +28,60 @@ import org.springframework.http.codec.KotlinSerializationBinaryEncoder;
* <a href="https://github.com/Kotlin/kotlinx.serialization">kotlinx.serialization</a>.
* It supports {@code application/x-protobuf}, {@code application/octet-stream}, and {@code application/vnd.google.protobuf}.
*
* <p>As of Spring Framework 7.0,
* <a href="https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#open-polymorphism">open polymorphism</a>
* is supported.
* <p>As of Spring Framework 7.0, by default it only encodes types annotated with
* {@link kotlinx.serialization.Serializable @Serializable} at type or generics
* level.
*
* <p>Alternative constructors with a {@code Predicate<ResolvableType>}
* parameter can be used to customize this behavior. For example,
* {@code new KotlinSerializationProtobufEncoder(type -> true)} will encode all types
* supported by Kotlin Serialization, including unannotated Kotlin enumerations,
* numbers, characters, booleans and strings.
*
* <p>Decoding streams is not supported yet, see
* <a href="https://github.com/Kotlin/kotlinx.serialization/issues/1073">kotlinx.serialization/issues/1073</a>
* related issue.
*
* @author Iain Henderson
* @author Sebastien Deleuze
* @since 6.0
* @see KotlinSerializationProtobufDecoder
*/
public class KotlinSerializationProtobufEncoder extends KotlinSerializationBinaryEncoder<ProtoBuf> {
/**
* Construct a new encoder using {@link ProtoBuf.Default} instance which
* only encodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationProtobufEncoder() {
this(ProtoBuf.Default);
}
/**
* Construct a new encoder using {@link ProtoBuf.Default} instance which
* only encodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationProtobufEncoder(Predicate<ResolvableType> typePredicate) {
this(ProtoBuf.Default, typePredicate);
}
/**
* Construct a new encoder using the provided {@link ProtoBuf} instance which
* only encodes types annotated with {@link kotlinx.serialization.Serializable @Serializable}
* at type or generics level.
*/
public KotlinSerializationProtobufEncoder(ProtoBuf protobuf) {
super(protobuf, ProtobufCodecSupport.MIME_TYPES);
}
/**
* Construct a new encoder using the provided {@link ProtoBuf} instance which
* only encodes types for which the specified predicate returns {@code true}.
* @since 7.0
*/
public KotlinSerializationProtobufEncoder(ProtoBuf protobuf, Predicate<ResolvableType> typePredicate) {
super(protobuf, typePredicate, ProtobufCodecSupport.MIME_TYPES);
}
}

29
spring-web/src/test/kotlin/org/springframework/http/codec/cbor/KotlinSerializationCborDecoderTests.kt

@ -49,22 +49,41 @@ class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializa @@ -49,22 +49,41 @@ class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializa
assertThat(decoder.canDecode(ResolvableType.forClass(String::class.java), null)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(OrderedImpl::class.java), MediaType.APPLICATION_CBOR)).isFalse()
}
@Test
fun canDecodeForAllTypes() {
val decoderForAllTypes = KotlinSerializationCborDecoder { true }
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java), null)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(String::class.java), null)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(OrderedImpl::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)
.verify()
}
@Test
@ -86,7 +105,7 @@ class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializa @@ -86,7 +105,7 @@ class KotlinSerializationCborDecoderTests : AbstractDecoderTests<KotlinSerializa
private fun byteBuffer(value: Any): Mono<DataBuffer> {
return Mono.defer {
val bytes = Cbor.Default.encodeToByteArray(serializer(Pojo::class.java), value)
val bytes = Cbor.encodeToByteArray(serializer(Pojo::class.java), value)
val buffer = bufferFactory.allocateBuffer(bytes.size)
buffer.write(bytes)
Mono.just(buffer)

22
spring-web/src/test/kotlin/org/springframework/http/codec/cbor/KotlinSerializationCborEncoderTests.kt

@ -49,10 +49,24 @@ class KotlinSerializationCborEncoderTests : AbstractEncoderTests<KotlinSerializa @@ -49,10 +49,24 @@ class KotlinSerializationCborEncoderTests : AbstractEncoderTests<KotlinSerializa
assertThat(encoder.canEncode(pojoType, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoder.canEncode(pojoType, null)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isFalse()
}
@Test
fun canEncodeForAllTypes() {
val encoderForAllTypes = KotlinSerializationCborEncoder { true }
val pojoType = ResolvableType.forClass(Pojo::class.java)
assertThat(encoderForAllTypes.canEncode(pojoType, MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoderForAllTypes.canEncode(pojoType, null)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_CBOR)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_CBOR)).isTrue()
}
@Test
@ -65,7 +79,7 @@ class KotlinSerializationCborEncoderTests : AbstractEncoderTests<KotlinSerializa @@ -65,7 +79,7 @@ class KotlinSerializationCborEncoderTests : AbstractEncoderTests<KotlinSerializa
pojo2,
pojo3
)
val pojoBytes = Cbor.Default.encodeToByteArray(arrayOf(pojo1, pojo2, pojo3))
val pojoBytes = Cbor.encodeToByteArray(arrayOf(pojo1, pojo2, pojo3))
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer> ->
step
.consumeNextWith(expectBytes(pojoBytes)

2
spring-web/src/test/kotlin/org/springframework/http/codec/json/CustomKotlinSerializationJsonDecoderTests.kt

@ -36,7 +36,7 @@ import java.nio.charset.StandardCharsets @@ -36,7 +36,7 @@ import java.nio.charset.StandardCharsets
* @author Sebastien Deleuze
*/
class CustomKotlinSerializationJsonDecoderTests :
AbstractDecoderTests<KotlinSerializationJsonDecoder>(KotlinSerializationJsonDecoder(customJson)) {
AbstractDecoderTests<KotlinSerializationJsonDecoder>(KotlinSerializationJsonDecoder(customJson) { true }) {
@Test
override fun canDecode() {

2
spring-web/src/test/kotlin/org/springframework/http/codec/json/CustomKotlinSerializationJsonEncoderTests.kt

@ -34,7 +34,7 @@ import java.math.BigDecimal @@ -34,7 +34,7 @@ import java.math.BigDecimal
* @author Sebastien Deleuze
*/
class CustomKotlinSerializationJsonEncoderTests :
AbstractEncoderTests<KotlinSerializationJsonEncoder>(KotlinSerializationJsonEncoder(customJson)) {
AbstractEncoderTests<KotlinSerializationJsonEncoder>(KotlinSerializationJsonEncoder(customJson) { true }) {
@Test
override fun canEncode() {

38
spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonDecoderTests.kt

@ -59,13 +59,13 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests<KotlinSerializa @@ -59,13 +59,13 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests<KotlinSerializa
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java),
MediaType("application", "json", StandardCharsets.ISO_8859_1))).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.NONE, MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(BigDecimal::class.java), MediaType.APPLICATION_JSON)).isFalse()
@ -73,6 +73,36 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests<KotlinSerializa @@ -73,6 +73,36 @@ class KotlinSerializationJsonDecoderTests : AbstractDecoderTests<KotlinSerializa
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_NDJSON)).isTrue()
}
@Test
fun canDecodeForAllTypes() {
val decoderForAllTypes = KotlinSerializationJsonDecoder { true }
val jsonSubtype = MediaType("application", "vnd.test-micro-type+json")
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java), jsonSubtype)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java), null)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(String::class.java), null)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java),
MediaType("application", "json", StandardCharsets.UTF_8))).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java),
MediaType("application", "json", StandardCharsets.US_ASCII))).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java),
MediaType("application", "json", StandardCharsets.ISO_8859_1))).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Ordered::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoderForAllTypes.canDecode(ResolvableType.NONE, MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(BigDecimal::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(decoderForAllTypes.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_NDJSON)).isTrue()
}
@Test
override fun decode() {
val input = Flux.concat(

58
spring-web/src/test/kotlin/org/springframework/http/codec/json/KotlinSerializationJsonEncoderTests.kt

@ -40,7 +40,6 @@ import kotlin.reflect.jvm.javaMethod @@ -40,7 +40,6 @@ import kotlin.reflect.jvm.javaMethod
*
* @author Sebastien Deleuze
*/
@Suppress("UsePropertyAccessSyntax")
class KotlinSerializationJsonEncoderTests : AbstractEncoderTests<KotlinSerializationJsonEncoder>(KotlinSerializationJsonEncoder()) {
@Test
@ -55,12 +54,51 @@ class KotlinSerializationJsonEncoderTests : AbstractEncoderTests<KotlinSerializa @@ -55,12 +54,51 @@ class KotlinSerializationJsonEncoderTests : AbstractEncoderTests<KotlinSerializa
assertThat(encoder.canEncode(ResolvableType.forClass(Pojo::class.java),
MediaType("application", "json", StandardCharsets.US_ASCII))).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(pojoType, MediaType.APPLICATION_NDJSON)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClass(String::class.java), null)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
val sseType = ResolvableType.forClass(ServerSentEvent::class.java)
assertThat(encoder.canEncode(sseType, MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClass(BigDecimal::class.java), null)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(encoder.canEncode(ResolvableType.NONE, MediaType.APPLICATION_JSON)).isFalse()
}
@Test
fun canEncodeForAllTypes() {
val encoderForAllTypes = KotlinSerializationJsonEncoder { true }
val pojoType = ResolvableType.forClass(Pojo::class.java)
val jsonSubtype = MediaType("application", "vnd.test-micro-type+json")
assertThat(encoderForAllTypes.canEncode(pojoType, MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoderForAllTypes.canEncode(pojoType, jsonSubtype)).isTrue()
assertThat(encoderForAllTypes.canEncode(pojoType, null)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClass(Pojo::class.java),
MediaType("application", "json", StandardCharsets.UTF_8))).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClass(Pojo::class.java),
MediaType("application", "json", StandardCharsets.US_ASCII))).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_JSON)).isTrue()
assertThat(encoderForAllTypes.canEncode(pojoType, MediaType.APPLICATION_NDJSON)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClass(String::class.java), null)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
val sseType = ResolvableType.forClass(ServerSentEvent::class.java)
assertThat(encoderForAllTypes.canEncode(sseType, MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClass(BigDecimal::class.java), null)).isFalse()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(encoderForAllTypes.canEncode(ResolvableType.NONE, MediaType.APPLICATION_JSON)).isFalse()
}
@Test
@ -158,18 +196,6 @@ class KotlinSerializationJsonEncoderTests : AbstractEncoderTests<KotlinSerializa @@ -158,18 +196,6 @@ class KotlinSerializationJsonEncoderTests : AbstractEncoderTests<KotlinSerializa
}
}
@Test
fun canNotEncode() {
assertThat(encoder.canEncode(ResolvableType.forClass(String::class.java), null)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
val sseType = ResolvableType.forClass(ServerSentEvent::class.java)
assertThat(encoder.canEncode(sseType, MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClass(BigDecimal::class.java), null)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, OrderedImpl::class.java), MediaType.APPLICATION_JSON)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
assertThat(encoder.canEncode(ResolvableType.NONE, MediaType.APPLICATION_JSON)).isFalse()
}
@Test
fun encodeProperty() {
val input = Mono.just(value)

6
spring-web/src/test/kotlin/org/springframework/http/codec/protobuf/KotlinSerializationProtobufDecoderTests.kt

@ -40,7 +40,7 @@ import reactor.test.StepVerifier.FirstStep @@ -40,7 +40,7 @@ import reactor.test.StepVerifier.FirstStep
* @author Iain Henderson
*/
@ExperimentalSerializationApi
class KotlinSerializationProtobufDecoderTests : AbstractDecoderTests<KotlinSerializationProtobufDecoder>(KotlinSerializationProtobufDecoder()) {
class KotlinSerializationProtobufDecoderTests : AbstractDecoderTests<KotlinSerializationProtobufDecoder>(KotlinSerializationProtobufDecoder { true }) {
@Test
override fun canDecode() {
@ -56,7 +56,7 @@ class KotlinSerializationProtobufDecoderTests : AbstractDecoderTests<KotlinSeria @@ -56,7 +56,7 @@ class KotlinSerializationProtobufDecoderTests : AbstractDecoderTests<KotlinSeria
assertThat(decoder.canDecode(ResolvableType.forClass(OrderedImpl::class.java), mimeType)).isFalse()
}
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), null)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClass(String::class.java), null)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClass(String::class.java), null)).isTrue()
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo::class.java), MediaType.APPLICATION_XML)).isFalse()
assertThat(decoder.canDecode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
}
@ -106,7 +106,7 @@ class KotlinSerializationProtobufDecoderTests : AbstractDecoderTests<KotlinSeria @@ -106,7 +106,7 @@ class KotlinSerializationProtobufDecoderTests : AbstractDecoderTests<KotlinSeria
private fun byteBuffer(value: Any): Mono<DataBuffer> {
return Mono.defer {
val bytes = ProtoBuf.Default.encodeToByteArray(serializer(Pojo::class.java), value)
val bytes = ProtoBuf.encodeToByteArray(serializer(Pojo::class.java), value)
val buffer = bufferFactory.allocateBuffer(bytes.size)
buffer.write(bytes)
Mono.just(buffer)

30
spring-web/src/test/kotlin/org/springframework/http/codec/protobuf/KotlinSerializationProtobufEncoderTests.kt

@ -29,6 +29,7 @@ import org.springframework.core.io.buffer.DataBufferUtils @@ -29,6 +29,7 @@ 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 org.springframework.http.codec.cbor.KotlinSerializationCborEncoder
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier.FirstStep
@ -57,14 +58,33 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria @@ -57,14 +58,33 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria
val mediaType = MediaType(mimeType)
assertThat(encoder.canEncode(pojoType, mediaType)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), mimeType)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), mimeType)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), mimeType)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), mimeType)).isFalse()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), mimeType)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), mimeType)).isTrue()
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), mimeType)).isFalse()
}
assertThat(encoder.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
}
@Test
fun canEncodeForAllTypes() {
val encoderForAllTypes = KotlinSerializationProtobufEncoder { true }
val pojoType = ResolvableType.forClass(Pojo::class.java)
assertThat(encoderForAllTypes.canEncode(pojoType, null)).isTrue()
for (mimeType in ProtobufCodecSupport.MIME_TYPES) {
val mediaType = MediaType(mimeType)
assertThat(encoderForAllTypes.canEncode(pojoType, mediaType)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Int::class.java), mimeType)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Ordered::class.java), mimeType)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(List::class.java, Pojo::class.java), mimeType)).isTrue()
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), mimeType)).isTrue()
}
assertThat(encoderForAllTypes.canEncode(ResolvableType.forClassWithGenerics(ArrayList::class.java, Int::class.java), MediaType.APPLICATION_PDF)).isFalse()
}
@Test
override fun encode() {
val pojo1 = Pojo("foo", "bar")
@ -75,7 +95,7 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria @@ -75,7 +95,7 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria
pojo2,
pojo3
)
val pojoBytes = ProtoBuf.Default.encodeToByteArray(arrayOf(pojo1, pojo2, pojo3))
val pojoBytes = ProtoBuf.encodeToByteArray(arrayOf(pojo1, pojo2, pojo3))
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer> ->
step
.consumeNextWith(expectBytes(pojoBytes)
@ -90,7 +110,7 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria @@ -90,7 +110,7 @@ class KotlinSerializationProtobufEncoderTests : AbstractEncoderTests<KotlinSeria
val input = Mono.just(pojo)
testEncode(input, Pojo::class.java) { step: FirstStep<DataBuffer> ->
step
.consumeNextWith(expectBytes(ProtoBuf.Default.encodeToByteArray(pojo))
.consumeNextWith(expectBytes(ProtoBuf.encodeToByteArray(pojo))
.andThen { dataBuffer: DataBuffer -> DataBufferUtils.release(dataBuffer) })
.verifyComplete()
}

Loading…
Cancel
Save