-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(generator): support
@JsonCreator
and @JsonValue
annotations …
…(#2381) * First implementation, throws if issues with annotations * Cache results * Simplify cache * Fix comment * Rename exception * Change import style * Fix test name * Simplify annotation search --------- Co-authored-by: Anton Platonov <[email protected]> Co-authored-by: Soroosh Taefi <[email protected]>
- Loading branch information
1 parent
f0388da
commit dcfac8e
Showing
9 changed files
with
418 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
...ugin-backbone/src/main/java/com/vaadin/hilla/parser/plugins/backbone/JsonValuePlugin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
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; | ||
import com.vaadin.hilla.parser.core.NodeDependencies; | ||
import com.vaadin.hilla.parser.core.NodePath; | ||
import com.vaadin.hilla.parser.models.*; | ||
import com.vaadin.hilla.parser.plugins.backbone.nodes.TypeSignatureNode; | ||
import jakarta.annotation.Nonnull; | ||
|
||
import java.lang.reflect.Method; | ||
import java.util.Arrays; | ||
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. | ||
*/ | ||
public class JsonValuePlugin | ||
extends AbstractPlugin<BackbonePluginConfiguration> { | ||
private final Map<Class<?>, Optional<Class<?>>> jsonValues = new HashMap<>(); | ||
|
||
@Override | ||
public void enter(NodePath<?> nodePath) { | ||
} | ||
|
||
@Override | ||
public void exit(NodePath<?> nodePath) { | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public NodeDependencies scan(@Nonnull NodeDependencies nodeDependencies) { | ||
return nodeDependencies; | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public Node<?, ?> resolve(@Nonnull Node<?, ?> node, | ||
@Nonnull NodePath<?> parentPath) { | ||
if (node instanceof TypeSignatureNode typeSignatureNode) { | ||
if (typeSignatureNode | ||
.getSource() instanceof ClassRefSignatureModel classRefSignatureModel) { | ||
var cls = (Class<?>) classRefSignatureModel.getClassInfo() | ||
.get(); | ||
// Check if the class has the annotations which qualify for a | ||
// value type. If so, replace the type with the corresponding | ||
// value type. | ||
Optional<TypeSignatureNode> valueNode = getValueType(cls) | ||
.map(SignatureModel::of).map(TypeSignatureNode::of); | ||
return valueNode.orElse(typeSignatureNode); | ||
} | ||
} | ||
|
||
return node; | ||
} | ||
|
||
private Optional<Class<?>> getValueType(Class<?> cls) { | ||
// Use cached results to avoid recomputing the value type. | ||
return jsonValues.computeIfAbsent(cls, this::findValueType); | ||
} | ||
|
||
private Optional<Class<?>> findValueType(Class<?> cls) { | ||
// First of all, we check that the `@JsonValue` annotation is | ||
// used on a method of the class. | ||
var jsonValue = Arrays.stream(cls.getMethods()) | ||
.filter(method -> method.isAnnotationPresent(JsonValue.class)) | ||
.map(Method::getReturnType).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); | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...n-backbone/src/test/java/com/vaadin/hilla/parser/plugins/backbone/jsonvalue/Endpoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.vaadin.hilla.parser.plugins.backbone.jsonvalue; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.TYPE) | ||
public @interface Endpoint { | ||
} |
61 changes: 61 additions & 0 deletions
61
...e/src/test/java/com/vaadin/hilla/parser/plugins/backbone/jsonvalue/JsonValueEndpoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package com.vaadin.hilla.parser.plugins.backbone.jsonvalue; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
import com.fasterxml.jackson.annotation.JsonValue; | ||
|
||
@Endpoint | ||
public class JsonValueEndpoint { | ||
public record Name(String firstName, String lastName) { | ||
@JsonCreator | ||
public Name(String fullName) { | ||
this(fullName.split(" ")[0], fullName.split(" ")[1]); | ||
} | ||
|
||
@JsonValue | ||
public String fullName() { | ||
return firstName + " " + lastName; | ||
} | ||
} | ||
|
||
public static class Email { | ||
private String value; | ||
|
||
@JsonCreator | ||
public Email(String value) { | ||
this.value = value; | ||
} | ||
|
||
@JsonValue | ||
public String getValue() { | ||
return value; | ||
} | ||
|
||
public void setValue(String value) { | ||
this.value = value; | ||
} | ||
} | ||
|
||
public record PhoneNumber(@JsonValue int value) { | ||
@JsonCreator | ||
public PhoneNumber { | ||
} | ||
} | ||
|
||
public record Person(Name name, Email email, PhoneNumber phoneNumber) { | ||
} | ||
|
||
public Person getPerson() { | ||
return new Person(new Name("John", "Doe"), | ||
new Email("[email protected]"), new PhoneNumber(1234567890)); | ||
} | ||
|
||
public void setPerson(Person person) { | ||
} | ||
|
||
public Email getEmail() { | ||
return new Email("[email protected]"); | ||
} | ||
|
||
public void setEmail(Email email) { | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
...kbone/src/test/java/com/vaadin/hilla/parser/plugins/backbone/jsonvalue/JsonValueTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.vaadin.hilla.parser.plugins.backbone.jsonvalue; | ||
|
||
import com.vaadin.hilla.parser.core.Parser; | ||
import com.vaadin.hilla.parser.plugins.backbone.BackbonePlugin; | ||
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.Set; | ||
|
||
public class JsonValueTest { | ||
private final TestHelper helper = new TestHelper(getClass()); | ||
|
||
@Test | ||
public void should_CorrectlyMapJsonValue() | ||
throws IOException, URISyntaxException { | ||
var openAPI = new Parser().classLoader(getClass().getClassLoader()) | ||
.classPath(Set.of(helper.getTargetDir().toString())) | ||
.endpointAnnotation(Endpoint.class.getName()) | ||
.addPlugin(new BackbonePlugin()).execute(); | ||
|
||
helper.executeParserWithConfig(openAPI); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...c/test/java/com/vaadin/hilla/parser/plugins/backbone/jsonvaluenojsoncreator/Endpoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.vaadin.hilla.parser.plugins.backbone.jsonvaluenojsoncreator; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.TYPE) | ||
public @interface Endpoint { | ||
} |
30 changes: 30 additions & 0 deletions
30
.../hilla/parser/plugins/backbone/jsonvaluenojsoncreator/JsonValueNoJsonCreatorEndpoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.vaadin.hilla.parser.plugins.backbone.jsonvaluenojsoncreator; | ||
|
||
import com.fasterxml.jackson.annotation.JsonValue; | ||
|
||
@Endpoint | ||
public class JsonValueNoJsonCreatorEndpoint { | ||
public static class Email { | ||
private String value; | ||
|
||
public Email(String value) { | ||
this.value = value; | ||
} | ||
|
||
@JsonValue | ||
public String getValue() { | ||
return value; | ||
} | ||
|
||
public void setValue(String value) { | ||
this.value = value; | ||
} | ||
} | ||
|
||
public Email getEmail() { | ||
return new Email("[email protected]"); | ||
} | ||
|
||
public void setEmail(Email email) { | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
...adin/hilla/parser/plugins/backbone/jsonvaluenojsoncreator/JsonValueNoJsonCreatorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.vaadin.hilla.parser.plugins.backbone.jsonvaluenojsoncreator; | ||
|
||
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.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().classLoader(getClass().getClassLoader()) | ||
.classPath(Set.of(helper.getTargetDir().toString())) | ||
.endpointAnnotation(Endpoint.class.getName()) | ||
.addPlugin(new BackbonePlugin()).execute(); | ||
}); | ||
} | ||
} |
Oops, something went wrong.