Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(engine): don't check for @JsonCreator annotation and only take @JsonValue into account #3097

Merged
merged 8 commits into from
Jan 14, 2025
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.vaadin.hilla.parser.plugins.backbone;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.vaadin.hilla.parser.core.AbstractPlugin;
import com.vaadin.hilla.parser.core.Node;
Expand All @@ -16,11 +15,9 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

/**
* Adds support for Jackson's {@code JsonValue} and {@code JsonCreator}
* annotations.
* Adds support for Jackson's {@code JsonValue} annotation.
*/
public class JsonValuePlugin
extends AbstractPlugin<BackbonePluginConfiguration> {
Expand Down Expand Up @@ -67,40 +64,8 @@ private Optional<Class<?>> getValueType(Class<?> cls) {
}

private Optional<Class<?>> findValueType(Class<?> cls) {
// First of all, we check that the `@JsonValue` annotation is
// used on a method of the class.
Stream<Class<?>> candidates = Arrays.stream(cls.getMethods())
return Arrays.stream(cls.getMethods())
.filter(method -> method.isAnnotationPresent(JsonValue.class))
.map(Method::getReturnType);
var jsonValue = candidates.findAny();

// Then we check that the class has a `@JsonCreator` annotation
// on a method or on a constructor. This is a basic check, we
// could also check that they use the same type.
var jsonCreator = Stream
.concat(Arrays.stream(cls.getMethods()),
Arrays.stream(cls.getConstructors()))
.filter(executable -> executable
.isAnnotationPresent(JsonCreator.class))
.findAny();

// Classes having only one of those annotation are malformed in Hilla as
// they break the generator or, at least, make data transfer impossible,
// so we throw an exception for those.
if (jsonValue.isPresent() ^ jsonCreator.isPresent()) {
throw new MalformedValueTypeException("Class " + cls.getName()
+ " has only one of @JsonValue and @JsonCreator."
+ " Hilla only supports classes with both annotations.");
}

return jsonValue;
}

// this shouldn't be a runtime exception, but `resolve` doesn't allow
// checked exceptions
public static class MalformedValueTypeException extends RuntimeException {
public MalformedValueTypeException(String message) {
super(message);
}
.map(Method::getReturnType).findAny();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.vaadin.hilla.parser.plugins.backbone.jsonvaluenojsoncreator;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

@Endpoint
public class JsonCreatorNoJsonValueEndpoint {

public static class User {

private final String name;
private final int age;

// Default constructor
public User() {
this.name = "Unknown";
this.age = 0;
}

// Constructor used during deserialization
@JsonCreator
public User(@JsonProperty("name") String n,
@JsonProperty("age") int a) {
name = n;
age = a;
}

public String getName() {
return name;
}

public int getAge() {
return age;
}
}

public User getUser() {
return new User("John Doe", 42);
}

public void setUser(User user) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@

import com.vaadin.hilla.parser.core.Parser;
import com.vaadin.hilla.parser.plugins.backbone.BackbonePlugin;
import com.vaadin.hilla.parser.plugins.backbone.JsonValuePlugin.MalformedValueTypeException;
import com.vaadin.hilla.parser.plugins.backbone.test.helpers.TestHelper;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertThrows;

public class JsonValueNoJsonCreatorTest {
private final TestHelper helper = new TestHelper(getClass());

@Test
public void should_ThrowExceptionWhenOnlyJsonValueIsUsed() {
assertThrows(MalformedValueTypeException.class, () -> {
new Parser().classPath(Set.of(helper.getTargetDir().toString()))
.endpointAnnotations(List.of(Endpoint.class))
.addPlugin(new BackbonePlugin())
.execute(List.of(JsonValueNoJsonCreatorEndpoint.class));
});
public void should_notChangeOutcomeAccordingToJsonCreator()
throws IOException, URISyntaxException {
var openAPI = new Parser()
.classPath(Set.of(helper.getTargetDir().toString()))
.endpointAnnotations(List.of(Endpoint.class))
.addPlugin(new BackbonePlugin())
.execute(List.of(JsonValueNoJsonCreatorEndpoint.class,
JsonCreatorNoJsonValueEndpoint.class));

helper.executeParserWithConfig(openAPI);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
{
"openapi": "3.0.1",
"info": {
"title": "Hilla Application",
"version": "1.0.0"
},
"servers": [
{
"url": "http://localhost:8080/connect",
"description": "Hilla Backend"
}
],
"tags": [
{
"name": "JsonValueNoJsonCreatorEndpoint",
"x-class-name": "com.vaadin.hilla.parser.plugins.backbone.jsonvaluenojsoncreator.JsonValueNoJsonCreatorEndpoint"
},
{
"name": "JsonCreatorNoJsonValueEndpoint",
"x-class-name": "com.vaadin.hilla.parser.plugins.backbone.jsonvaluenojsoncreator.JsonCreatorNoJsonValueEndpoint"
}
],
"paths": {
"/JsonValueNoJsonCreatorEndpoint/getEmail": {
"post": {
"tags": [
"JsonValueNoJsonCreatorEndpoint"
],
"operationId": "JsonValueNoJsonCreatorEndpoint_getEmail_POST",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "string",
"nullable": true
}
}
}
}
}
}
},
"/JsonValueNoJsonCreatorEndpoint/setEmail": {
"post": {
"tags": [
"JsonValueNoJsonCreatorEndpoint"
],
"operationId": "JsonValueNoJsonCreatorEndpoint_setEmail_POST",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"email": {
"type": "string",
"nullable": true
}
}
}
}
}
},
"responses": {
"200": {
"description": ""
}
}
}
},
"/JsonCreatorNoJsonValueEndpoint/getUser": {
"post": {
"tags": [
"JsonCreatorNoJsonValueEndpoint"
],
"operationId": "JsonCreatorNoJsonValueEndpoint_getUser_POST",
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"nullable": true,
"anyOf": [
{
"$ref": "#/components/schemas/com.vaadin.hilla.parser.plugins.backbone.jsonvaluenojsoncreator.JsonCreatorNoJsonValueEndpoint$User"
}
]
}
}
}
}
}
}
},
"/JsonCreatorNoJsonValueEndpoint/setUser": {
"post": {
"tags": [
"JsonCreatorNoJsonValueEndpoint"
],
"operationId": "JsonCreatorNoJsonValueEndpoint_setUser_POST",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"user": {
"nullable": true,
"anyOf": [
{
"$ref": "#/components/schemas/com.vaadin.hilla.parser.plugins.backbone.jsonvaluenojsoncreator.JsonCreatorNoJsonValueEndpoint$User"
}
]
}
}
}
}
}
},
"responses": {
"200": {
"description": ""
}
}
}
}
},
"components": {
"schemas": {
"com.vaadin.hilla.parser.plugins.backbone.jsonvaluenojsoncreator.JsonCreatorNoJsonValueEndpoint$User": {
"type": "object",
"properties": {
"name": {
"type": "string",
"nullable": true
},
"age": {
"type": "integer",
"format": "int32"
}
}
}
}
}
}
Loading