Skip to content

Commit

Permalink
Merge branch 'main' into fix/3099/Server-side-validators-not-working-…
Browse files Browse the repository at this point in the history
…correctly-with-array-form-binding
  • Loading branch information
cromoteca authored Jan 14, 2025
2 parents 5e67f69 + efc7da0 commit e7673d6
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 48 deletions.
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"
}
}
}
}
}
}

0 comments on commit e7673d6

Please sign in to comment.