Skip to content

Commit

Permalink
Include serdeable metadata to internal JSON objects (#10526)
Browse files Browse the repository at this point in the history
  • Loading branch information
dstepanov committed Feb 26, 2024
1 parent 8c12ad3 commit 2a4e52f
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 11 deletions.
14 changes: 12 additions & 2 deletions discovery-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,19 @@ plugins {
dependencies {
annotationProcessor project(":inject-java")
annotationProcessor project(":graal")

annotationProcessor(platform(libs.test.boms.micronaut.serde))
annotationProcessor(libs.micronaut.serde.processor) {
exclude group: "io.micronaut", module: "json-core"
}

api project(':context')
implementation libs.managed.reactor

compileOnly project(":jackson-databind")
testImplementation project(":jackson-databind")
// api project(":http")
compileOnly(platform(libs.test.boms.micronaut.serde))
compileOnly(libs.micronaut.serde.api) {
exclude group: "io.micronaut", module: "json-core"
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
package io.micronaut.health;

import com.fasterxml.jackson.annotation.JsonValue;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.ReflectiveAccess;
import io.micronaut.serde.annotation.Serdeable;

import java.util.Optional;

Expand All @@ -29,7 +29,7 @@
* @author Graeme Rocher
* @since 1.0
*/
@Introspected
@Serdeable
@ReflectiveAccess
public class HealthStatus implements Comparable<HealthStatus> {

Expand Down
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ managed-snakeyaml = "2.2"
managed-java-parser-core = "3.25.8"
managed-ksp = "1.9.22-1.0.17"
micronaut-docs = "2.0.0"
micronaut-serde = "2.8.1"

[libraries]
# Libraries prefixed with bom- are BOM files
Expand All @@ -86,6 +87,7 @@ test-boms-micronaut-validation = { module = "io.micronaut.validation:micronaut-v
test-boms-micronaut-rxjava2 = { module = "io.micronaut.rxjava2:micronaut-rxjava2-bom", version.ref = "micronaut-rxjava2" }
test-boms-micronaut-rxjava3 = { module = "io.micronaut.rxjava3:micronaut-rxjava3-bom", version.ref = "micronaut-rxjava3" }
test-boms-micronaut-reactor = { module = "io.micronaut.reactor:micronaut-reactor-bom", version.ref = "micronaut-reactor" }
test-boms-micronaut-serde = { module = "io.micronaut.serde:micronaut-serde-bom", version.ref = "micronaut-serde" }

boms-groovy = { module = "org.apache.groovy:groovy-bom", version.ref = "managed-groovy" }
boms-kotlin = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "managed-kotlin" }
Expand Down Expand Up @@ -262,6 +264,10 @@ micronaut-tracing-brave = { module = "io.micronaut.tracing:micronaut-tracing-bra
micronaut-validation = { module = "io.micronaut.validation:micronaut-validation" }
micronaut-validation-processor = { module = "io.micronaut.validation:micronaut-validation-processor" }

micronaut-serde-api = { module = "io.micronaut.serde:micronaut-serde-api" }
micronaut-serde-processor = { module = "io.micronaut.serde:micronaut-serde-processor" }
micronaut-serde-jackson = { module = "io.micronaut.serde:micronaut-serde-jackson" }

testcontainers-spock = { module = "org.testcontainers:spock", version.ref = "testcontainers" }

vertx = { module = "io.vertx:vertx-core", version.ref = "vertx" }
Expand Down
11 changes: 11 additions & 0 deletions http/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@ plugins {
dependencies {
annotationProcessor project(":inject-java")
annotationProcessor project(":graal")

annotationProcessor(platform(libs.test.boms.micronaut.serde))
annotationProcessor(libs.micronaut.serde.processor) {
exclude group: "io.micronaut", module: "json-core"
}

api project(":context")
api project(":core-reactive")
implementation project(":context-propagation")
implementation libs.managed.reactor
compileOnly libs.managed.kotlinx.coroutines.core
compileOnly libs.managed.kotlinx.coroutines.reactor

compileOnly(platform(libs.test.boms.micronaut.serde))
compileOnly(libs.micronaut.serde.api) {
exclude group: "io.micronaut", module: "json-core"
}

compileOnly libs.managed.jackson.annotations

testCompileOnly project(":inject-groovy")
Expand Down
14 changes: 12 additions & 2 deletions http/src/main/java/io/micronaut/http/hateoas/AbstractResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,18 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.ReflectiveAccess;
import io.micronaut.core.convert.value.ConvertibleValues;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.value.OptionalMultiValues;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Produces;
import io.micronaut.serde.annotation.Serdeable;

import io.micronaut.core.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -41,8 +43,9 @@
* @since 1.1
*/
@Produces(MediaType.APPLICATION_HAL_JSON)
@Serdeable
@Introspected
public abstract class AbstractResource<Impl extends AbstractResource> implements Resource {
public abstract class AbstractResource<Impl extends AbstractResource<Impl>> implements Resource {

private final Map<CharSequence, List<Link>> linkMap = new LinkedHashMap<>(1);
private final Map<CharSequence, List<Resource>> embeddedMap = new LinkedHashMap<>(1);
Expand Down Expand Up @@ -150,6 +153,13 @@ public final void setLinks(Map<String, Object> links) {
if (value instanceof Map) {
Map<String, Object> linkMap = (Map<String, Object>) value;
link(name, linkMap);
} else if (value instanceof Collection<?> collection) {
for (Object o : collection) {
if (o instanceof Map) {
Map<String, Object> linkMap = (Map<String, Object>) o;
link(name, linkMap);
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.util.ObjectUtils;
import io.micronaut.serde.annotation.Serdeable;

import java.util.LinkedHashMap;
import java.util.Map;
Expand All @@ -31,7 +31,7 @@
* @since 3.4.0
* @author yawkat
*/
@Introspected
@Serdeable
public final class GenericResource extends AbstractResource<GenericResource> {
private final Map<String, Object> additionalProperties = new LinkedHashMap<>();

Expand Down
3 changes: 3 additions & 0 deletions http/src/main/java/io/micronaut/http/hateoas/JsonError.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import io.micronaut.http.annotation.Produces;

import io.micronaut.core.annotation.Nullable;
import io.micronaut.serde.annotation.Serdeable;

import java.util.Optional;

/**
Expand All @@ -30,6 +32,7 @@
* @author Graeme Rocher
* @since 1.1
*/
@Serdeable
@Produces(MediaType.APPLICATION_JSON)
public class JsonError extends AbstractResource<JsonError> {

Expand Down
2 changes: 2 additions & 0 deletions http/src/main/java/io/micronaut/http/hateoas/Resource.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.value.OptionalMultiValues;
import io.micronaut.serde.annotation.Serdeable;

/**
* Represents a REST resource in a hateoas architecture.
*
* @author Graeme Rocher
* @since 1.1
*/
@Serdeable
@Introspected
public interface Resource {

Expand Down
2 changes: 2 additions & 0 deletions http/src/main/java/io/micronaut/http/hateoas/VndError.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Produces;
import io.micronaut.serde.annotation.Serdeable;

import java.util.List;

Expand All @@ -29,6 +30,7 @@
* @since 1.1
*/
@Produces(MediaType.APPLICATION_VND_ERROR)
@Serdeable
public class VndError extends JsonError {

/**
Expand Down
10 changes: 10 additions & 0 deletions management/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ dependencies {
annotationProcessor project(":inject-java")
annotationProcessor project(":graal")

annotationProcessor(platform(libs.test.boms.micronaut.serde))
annotationProcessor(libs.micronaut.serde.processor) {
exclude group: "io.micronaut", module: "json-core"
}

api project(":router")
api project(":discovery-core")
compileOnly project(":jackson-databind")
Expand Down Expand Up @@ -39,4 +44,9 @@ dependencies {
compileOnly libs.logback.classic
compileOnly libs.log4j

compileOnly(platform(libs.test.boms.micronaut.serde))
compileOnly(libs.micronaut.serde.api) {
exclude group: "io.micronaut", module: "json-core"
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
package io.micronaut.management.health.indicator;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.ReflectiveAccess;
import io.micronaut.health.HealthStatus;
import io.micronaut.serde.annotation.Serdeable;
import jakarta.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -32,9 +32,9 @@
* @author James Kleeh
* @since 1.0
*/
@Introspected
@ReflectiveAccess
@Serdeable
@JsonDeserialize(as = DefaultHealthResult.class)
@ReflectiveAccess
public interface HealthResult {

/**
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ include "test-suite-logback"
include "test-suite-logback-external-configuration"
include "test-suite-logback-graalvm"
include "test-suite-netty-ssl-graalvm"
include "test-suite-serde"
include "test-utils"

// benchmarks
Expand Down
18 changes: 18 additions & 0 deletions test-suite-serde/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
id "io.micronaut.build.internal.convention-test-library"
}

micronautBuild {
core {
usesMicronautTestSpock()
}
}

dependencies {
testAnnotationProcessor(projects.injectJava)
testImplementation(projects.http)
testImplementation(projects.management)
testImplementation(platform(libs.test.boms.micronaut.serde))
testImplementation libs.micronaut.serde.jackson
testImplementation(projects.micronaut.jacksonDatabind)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package io.micronaut.health

import io.micronaut.core.type.Argument
import io.micronaut.jackson.databind.JacksonDatabindMapper
import io.micronaut.json.JsonMapper
import io.micronaut.management.health.indicator.HealthResult
import io.micronaut.serde.ObjectMapper
import spock.lang.Specification

class HealthSpec extends Specification {

void "test HealthResult"(JsonMapper objectMapper) {
given:

HealthResult hr = HealthResult.builder("db", HealthStatus.DOWN)
.details(Collections.singletonMap("foo", "bar"))
.build()

when:
def result = objectMapper.writeValueAsString(hr)

then:
result == '{"name":"db","status":"DOWN","details":{"foo":"bar"}}'

when:
hr = objectMapper.readValue(result, Argument.of(HealthResult))

then:
hr.name == 'db'
hr.status == HealthStatus.DOWN

where:
objectMapper << [ObjectMapper.getDefault(), new JacksonDatabindMapper(new com.fasterxml.jackson.databind.ObjectMapper())]
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.micronaut.http.hateoas

import io.micronaut.jackson.databind.JacksonDatabindMapper
import io.micronaut.json.JsonMapper
import io.micronaut.serde.ObjectMapper
import spock.lang.PendingFeature
import spock.lang.Specification

class JsonErrorSpec extends Specification {

def jsonError = '{"_links":{"self":[{"href":"/resolve","templated":false}]},"_embedded":{"errors":[{"message":"Internal Server Error: Something bad happened"}]},"message":"Internal Server Error"}'

@PendingFeature
void "JsonError should be deserializable from a string - serde"() {
setup:
ObjectMapper objectMapper = ObjectMapper.getDefault()
when:
JsonError jsonError = objectMapper.readValue(this.jsonError, JsonError)

then:
jsonError.message == 'Internal Server Error'
jsonError.embedded.getFirst('errors').isPresent()
jsonError.links.getFirst("self").get().href == "/resolve"
!jsonError.links.getFirst("self").get().templated
}

@PendingFeature
void "can deserialize a Json error as a generic resource - serde"() {
setup:
ObjectMapper objectMapper = ObjectMapper.getDefault()
when:
GenericResource resource = objectMapper.readValue(jsonError, Resource)
then:
resource.embedded.getFirst('errors').isPresent()
resource.links.getFirst("self").get().href == "/resolve"
!resource.links.getFirst("self").get().templated
}

void "JsonError should be deserializable from a string - jackson databind"() {
setup:
JsonMapper objectMapper = new JacksonDatabindMapper(new com.fasterxml.jackson.databind.ObjectMapper())

when:
JsonError jsonError = objectMapper.readValue(this.jsonError, JsonError)

then:
jsonError.message == 'Internal Server Error'
jsonError.embedded.getFirst('errors').isPresent()
jsonError.links.getFirst("self").get().href == "/resolve"
!jsonError.links.getFirst("self").get().templated
}

void "can deserialize a Json error as a generic resource - jackson databind"() {
setup:
JsonMapper objectMapper = new JacksonDatabindMapper(new com.fasterxml.jackson.databind.ObjectMapper())
when:
GenericResource resource = objectMapper.readValue(jsonError, Resource)
then:
resource.embedded.getFirst('errors').isPresent()
resource.links.getFirst("self").get().href == "/resolve"
!resource.links.getFirst("self").get().templated
}
}

0 comments on commit 2a4e52f

Please sign in to comment.