Skip to content

Commit

Permalink
Examples depend on wirespec integration for default serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
nsimonides authored and jerrevanveluw committed Jan 13, 2025
1 parent 9a88fe3 commit 279d16b
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 169 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package community.flock.wirespec.example.maven.custom.app.common

import community.flock.wirespec.kotlin.Wirespec
import community.flock.wirespec.kotlin.serde.DefaultParamSerde
import community.flock.wirespec.kotlin.serde.DefaultParamSerialization
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.request
Expand Down Expand Up @@ -36,8 +36,15 @@ class WirespecClient(private val httpClient: HttpClient = HttpClient()) {
}
}

/**
* Example implementation of Wirespec Serialization using DefaultParamSerialization
* This class handles standard parameter serialization for headers and query parameters.
* For custom serialization requirements, you can create your own implementation
* of Wirespec.ParamSerialization instead of using DefaultParamSerialization.
* In this case, you don't need the dependency on community.flock.wirespec.integration:wirespec
*/
@Suppress("UNCHECKED_CAST")
object Serialization : Wirespec.Serialization<String>, Wirespec.ParamSerialization by DefaultParamSerde() {
object Serialization : Wirespec.Serialization<String>, Wirespec.ParamSerialization by DefaultParamSerialization() {
override fun <T> serialize(t: T, kType: KType): String =
Json.encodeToString(Json.serializersModule.serializer(kType), t)

Expand Down
6 changes: 5 additions & 1 deletion examples/maven-spring-compile/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>community.flock.wirespec.integration</groupId>
<artifactId>wirespec-jvm</artifactId>
<version>0.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,24 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import community.flock.wirespec.example.maven.custom.app.exception.SerializationException;
import community.flock.wirespec.java.Wirespec;
import community.flock.wirespec.java.serde.DefaultParamSerialization;
import org.springframework.stereotype.Component;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* Example implementation of Wirespec Serialization using DefaultParamSerialization
* This class handles standard parameter serialization for headers and query parameters.
* For custom serialization requirements, you can create your own implementation
* of Wirespec.ParamSerialization instead of using DefaultParamSerialization.
* In this case, you don't need the dependency on community.flock.wirespec.integration:wirespec
*/
@Component
public class WirespecSerializer implements Wirespec.Serialization<String> {
public class WirespecSerializer implements Wirespec.Serialization<String>, DefaultParamSerialization {

private final ObjectMapper objectMapper;
private final Map<Class<?>, Function<String, Object>> primitiveTypesConversion;

public WirespecSerializer(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.primitiveTypesConversion = initPrimitiveTypesConversion();
}

private Map<Class<?>, Function<String, Object>> initPrimitiveTypesConversion() {
Map<Class<?>, Function<String, Object>> conversion = new HashMap<>();
conversion.put(String.class, s -> s);
conversion.put(Integer.class, Integer::valueOf);
conversion.put(Long.class, Long::valueOf);
conversion.put(Double.class, Double::valueOf);
conversion.put(Float.class, Float::valueOf);
conversion.put(Boolean.class, Boolean::valueOf);
conversion.put(Character.class, s -> s.charAt(0));
conversion.put(Byte.class, Byte::valueOf);
conversion.put(Short.class, Short::valueOf);
return conversion;
}

@Override
Expand All @@ -52,19 +36,6 @@ public <T> String serialize(T body, Type type) {
}
}

@Override
public <T> List<String> serializeParam(T value, Type type) {
if (value == null) {
return null;
}
if (isIterable(type)) {
return StreamSupport.stream(((Iterable<?>) value).spliterator(), false)
.map(Object::toString)
.collect(Collectors.toList());
}
return List.of(value.toString());
}

@Override
@SuppressWarnings("unchecked")
public <T> T deserialize(String raw, Type valueType) {
Expand All @@ -80,92 +51,4 @@ public <T> T deserialize(String raw, Type valueType) {
throw new SerializationException(e);
}
}

@Override
@SuppressWarnings("unchecked")
public <T> T deserializeParam(List<String> values, Type type) {
if (values == null || values.isEmpty()) {
return null;
}
if (isIterable(type)) {
return (T) deserializeList(values, getIterableElementType(type));
}
if (isWirespecEnum(type)) {
return (T) deserializeEnum(values, (Class<?>) type);
}
return (T) deserializePrimitive(values, (Class<?>) type);
}

private List<Object> deserializeList(List<String> values, Type type) {
if (isWirespecEnum(type)) {
return values.stream()
.map(value -> findEnumByLabel((Class<?>) type, value))
.collect(Collectors.toList());
}
return deserializePrimitiveList(values, (Class<?>) type);
}

private Object deserializePrimitive(List<String> values, Class<?> clazz) {
String value = values.stream().findFirst()
.orElseThrow(() -> new IllegalArgumentException("No value provided for type: " + clazz.getSimpleName()));

Function<String, Object> converter = primitiveTypesConversion.get(clazz);
if (converter == null) {
throw new IllegalArgumentException("Unsupported primitive type: " + clazz.getSimpleName());
}
return converter.apply(value);
}

private List<Object> deserializePrimitiveList(List<String> values, Class<?> clazz) {
Function<String, Object> converter = primitiveTypesConversion.get(clazz);
if (converter == null) {
throw new IllegalArgumentException("Unsupported list element type: " + clazz.getSimpleName());
}
return values.stream()
.map(converter)
.collect(Collectors.toList());
}

private Object deserializeEnum(List<String> values, Class<?> enumClass) {
String value = values.stream().findFirst()
.orElseThrow(() -> new IllegalArgumentException("No enum value provided for type: " + enumClass.getSimpleName()));
return findEnumByLabel(enumClass, value);
}

private Object findEnumByLabel(Class<?> enumClass, String label) {
for (Object enumConstant : enumClass.getEnumConstants()) {
if (enumConstant instanceof Wirespec.Enum &&
((Wirespec.Enum) enumConstant).getLabel().equals(label)) {
return enumConstant;
}
}
throw new IllegalArgumentException("Invalid enum value '" + label + "' for type: " + enumClass.getSimpleName());
}

private boolean isIterable(Type type) {
return type instanceof ParameterizedType &&
Iterable.class.isAssignableFrom((Class<?>) ((ParameterizedType) type).getRawType());
}

private boolean isWirespecEnum(Type type) {
if (type instanceof Class<?>) {
Class<?> clazz = (Class<?>) type;
for (Class<?> iface : clazz.getInterfaces()) {
if (iface == Wirespec.Enum.class) {
return true;
}
}
}
return false;
}

private Type getIterableElementType(Type type) {
if (type instanceof ParameterizedType) {
Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
if (typeArguments.length > 0) {
return typeArguments[0];
}
}
return null;
}
}
5 changes: 5 additions & 0 deletions examples/maven-spring-convert/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-reactor</artifactId>
</dependency>
<dependency>
<groupId>community.flock.wirespec.integration</groupId>
<artifactId>wirespec-jvm</artifactId>
<version>0.0.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.RequestEntity
import org.springframework.stereotype.Component
import org.springframework.util.CollectionUtils.toMultiValueMap
import org.springframework.web.client.RestTemplate
import org.springframework.web.client.exchange

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,26 @@ package community.flock.wirespec.examples.openapi.app

import com.fasterxml.jackson.databind.ObjectMapper
import community.flock.wirespec.kotlin.Wirespec
import community.flock.wirespec.kotlin.serde.DefaultParamSerialization
import org.springframework.stereotype.Component
import kotlin.reflect.KType
import kotlin.reflect.javaType

/**
* NOTE: this Serialization is quite primitive, a real-world Serialization would be more complex.
* See community.flock.wirespec.integration.spring.kotlin.configuration.WirespecSerializationConfiguration for a more complete example
* Example implementation of Wirespec Serialization using DefaultParamSerialization
* This class handles standard parameter serialization for headers and query parameters.
* For custom serialization requirements, you can create your own implementation
* of Wirespec.ParamSerialization instead of using DefaultParamSerialization.
* In this case, you don't need the dependency on community.flock.wirespec.integration:wirespec
*/
@Component
@OptIn(ExperimentalStdlibApi::class)
class Serialization(private val objectMapper: ObjectMapper) : Wirespec.Serialization<String> {
class Serialization(private val objectMapper: ObjectMapper)
: Wirespec.Serialization<String>, Wirespec.ParamSerialization by DefaultParamSerialization() {

override fun <T> serialize(t: T, kType: KType): String = objectMapper.writeValueAsString(t)

override fun <T> serializeParam(value: T, kType: KType): List<String> =
listOf(objectMapper.writeValueAsString(value))

override fun <T> deserialize(raw: String, kType: KType): T = objectMapper
.constructType(kType.javaType)
.let { objectMapper.readValue(raw, it) }

override fun <T> deserializeParam(values: List<String>, kType: KType): T {
return objectMapper.constructType(kType.javaType).let { objectMapper.readValue(values.first(), it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import community.flock.wirespec.integration.jackson.java.WirespecModuleJava
import community.flock.wirespec.integration.spring.java.web.WirespecResponseBodyAdvice
import community.flock.wirespec.java.Wirespec
import community.flock.wirespec.java.serde.DefaultParamSerde
import community.flock.wirespec.java.serde.DefaultParamSerialization
import java.lang.reflect.Type
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
Expand All @@ -15,11 +15,10 @@ import org.springframework.context.annotation.Import
open class WirespecSerializationConfiguration {

@Bean
open fun queryParamSerde(): DefaultParamSerde =
DefaultParamSerde()
open fun queryParamSerde(): DefaultParamSerialization = DefaultParamSerialization.create()

@Bean
open fun wirespecSerialization(objectMapper: ObjectMapper, paramSerde: DefaultParamSerde): Wirespec.Serialization<String> =
open fun wirespecSerialization(objectMapper: ObjectMapper, paramSerde: DefaultParamSerialization): Wirespec.Serialization<String> =
object : Wirespec.Serialization<String>, Wirespec.ParamSerialization by paramSerde {
private val wirespecObjectMapper = objectMapper.copy().registerModule(WirespecModuleJava())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import community.flock.wirespec.integration.jackson.kotlin.WirespecModuleKotlin
import community.flock.wirespec.integration.spring.kotlin.web.WirespecResponseBodyAdvice
import community.flock.wirespec.kotlin.Wirespec
import community.flock.wirespec.kotlin.Wirespec.ParamSerialization
import community.flock.wirespec.kotlin.serde.DefaultParamSerde
import community.flock.wirespec.kotlin.serde.DefaultParamSerialization
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
Expand All @@ -18,7 +18,7 @@ import kotlin.reflect.javaType
open class WirespecSerializationConfiguration {

@Bean
open fun queryParamSerde(): ParamSerialization = DefaultParamSerde()
open fun queryParamSerde(): ParamSerialization = DefaultParamSerialization()

@Bean
open fun wirespecSerialization(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,32 @@

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.Map;
import java.util.List;
import java.util.Collections;
import java.util.Arrays;
import java.util.function.Function;

public class DefaultParamSerde implements Wirespec.ParamSerialization {

private final Map<Class<?>, Function<String, Object>> primitiveTypesConversion;

public DefaultParamSerde() {
primitiveTypesConversion = new HashMap<>();
primitiveTypesConversion.put(String.class, s -> s);
primitiveTypesConversion.put(Integer.class, Integer::parseInt);
primitiveTypesConversion.put(Long.class, Long::parseLong);
primitiveTypesConversion.put(Double.class, Double::parseDouble);
primitiveTypesConversion.put(Float.class, Float::parseFloat);
primitiveTypesConversion.put(Boolean.class, Boolean::parseBoolean);
primitiveTypesConversion.put(Character.class, s -> s.charAt(0));
primitiveTypesConversion.put(Byte.class, Byte::parseByte);
primitiveTypesConversion.put(Short.class, Short::parseShort);
public interface DefaultParamSerialization extends Wirespec.ParamSerialization {

static DefaultParamSerialization create() {
return new DefaultParamSerialization() {};
}

Map<Class<?>, Function<String, Object>> PRIMITIVE_TYPES_CONVERSION = Map.of(
String.class, s -> s,
Integer.class, Integer::parseInt,
Long.class, Long::parseLong,
Double.class, Double::parseDouble,
Float.class, Float::parseFloat,
Boolean.class, Boolean::parseBoolean,
Character.class, s -> s.charAt(0),
Byte.class, Byte::parseByte,
Short.class, Short::parseShort
);

@Override
public <T> List<String> serializeParam(T value, Type type) {
default <T> List<String> serializeParam(T value, Type type) {
if (isList(type)) {
return ((List<?>) value).stream()
.map(Object::toString)
Expand All @@ -36,7 +40,7 @@ public <T> List<String> serializeParam(T value, Type type) {

@Override
@SuppressWarnings("unchecked")
public <T> T deserializeParam(List<String> values, Type type) {
default <T> T deserializeParam(List<String> values, Type type) {
if (isList(type)) {
return (T) deserializeList(values, type);
}
Expand All @@ -63,15 +67,15 @@ private Object deserializePrimitive(List<String> values, Class<?> clazz) {
String value = values.stream().findFirst()
.orElseThrow(() -> new IllegalArgumentException("No value provided for type: " + clazz.getSimpleName()));

Function<String, Object> converter = primitiveTypesConversion.get(clazz);
Function<String, Object> converter = PRIMITIVE_TYPES_CONVERSION.get(clazz);
if (converter == null) {
throw new IllegalArgumentException("Unsupported primitive type: " + clazz.getSimpleName());
}
return converter.apply(value);
}

private List<Object> deserializePrimitiveList(List<String> values, Class<?> elementClass) {
Function<String, Object> converter = primitiveTypesConversion.get(elementClass);
Function<String, Object> converter = PRIMITIVE_TYPES_CONVERSION.get(elementClass);
if (converter == null) {
throw new IllegalArgumentException("Unsupported list element type: " + elementClass.getSimpleName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import community.flock.wirespec.kotlin.Wirespec
import kotlin.reflect.KClass
import kotlin.reflect.KType

class DefaultParamSerde : Wirespec.ParamSerialization {
class DefaultParamSerialization : Wirespec.ParamSerialization {

private val primitiveTypesConversion = mapOf<KClass<*>, (String) -> Any>(
String::class to { this },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import kotlin.reflect.KType
import kotlin.reflect.typeOf
import kotlin.test.*

class DefaultParamSerdeTest {
class DefaultParamSerializationTest {

private val serde = DefaultParamSerde()
private val serde = DefaultParamSerialization()

@Test
fun `should serialize primitive types correctly`() {
Expand Down

0 comments on commit 279d16b

Please sign in to comment.