-
Notifications
You must be signed in to change notification settings - Fork 65
Fix #3428 Fixed subtype hierarchy search and property names #3429
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,17 +20,19 @@ | |
| import io.swagger.v3.oas.models.media.ObjectSchema; | ||
| import io.swagger.v3.oas.models.media.Schema; | ||
| import io.swagger.v3.oas.models.media.StringSchema; | ||
|
|
||
| import org.apache.commons.lang3.StringUtils; | ||
| import org.apache.commons.lang3.tuple.Pair; | ||
| import org.jspecify.annotations.NonNull; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.Collection; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Stream; | ||
|
|
||
| /** | ||
| * This plugin adds support for {@code @JsonTypeInfo} and {@code @JsonSubTypes}. | ||
| * This plugin adds support for {@code @JsonTypeInfo} and | ||
| * {@code @JsonSubTypes}. | ||
| */ | ||
| public final class SubTypesPlugin extends AbstractPlugin<PluginConfiguration> { | ||
| @Override | ||
|
|
@@ -50,20 +52,19 @@ public void exit(NodePath<?> nodePath) { | |
| if (cls.getAnnotationsByType(JsonTypeInfo.class).length > 0) { | ||
| var schema = (Schema<?>) unionNode.getTarget(); | ||
| getJsonSubTypes(cls).map(JsonSubTypes.Type::value) | ||
| .forEach(c -> { | ||
| schema.addOneOfItem(new Schema<Object>() { | ||
| { | ||
| set$ref("#/components/schemas/" | ||
| + c.getName()); | ||
| } | ||
| }); | ||
| .forEach(c -> { | ||
| schema.addOneOfItem(new Schema<>() { | ||
| { | ||
| set$ref("#/components/schemas/" + c.getName()); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| // attach the schema to the openapi | ||
| EntityPlugin.attachSchemaWithNameToOpenApi(unionNode.getTarget(), | ||
| cls.getName() + "Union", | ||
| (OpenAPI) nodePath.getParentPath().getNode().getTarget()); | ||
| cls.getName() + "Union", | ||
| (OpenAPI) nodePath.getParentPath().getNode().getTarget()); | ||
| } | ||
|
|
||
| // entity nodes whose superclass has a @JsonSubTypes annotation must | ||
|
|
@@ -72,23 +73,35 @@ public void exit(NodePath<?> nodePath) { | |
| var entityNode = (EntityNode) nodePath.getNode(); | ||
| var cls = (Class<?>) entityNode.getSource().get(); | ||
|
|
||
| Optional.ofNullable(cls.getSuperclass()) | ||
| .map(SubTypesPlugin::getJsonSubTypes).stream() | ||
| .flatMap(Function.identity()) | ||
| .filter(t -> cls.equals(t.value())).findAny() | ||
| .ifPresent(t -> { | ||
| var schema = (ComposedSchema) entityNode.getTarget(); | ||
| schema.getAnyOf().stream() | ||
| getJsonSubTypeInHierarchy(cls).ifPresent(foundSubTypeInfo -> { | ||
| JsonTypeInfo info = foundSubTypeInfo.getLeft(); | ||
| JsonSubTypes.Type[] types = foundSubTypeInfo.getRight(); | ||
|
|
||
| String property = StringUtils.isNotBlank(info.property()) ? | ||
| info.property() : | ||
| info.use().getDefaultPropertyName(); | ||
|
|
||
| Arrays.stream(types).filter(e -> e.value().equals(cls)) | ||
| .findAny().ifPresent(t -> { | ||
| var schema = entityNode.getTarget(); | ||
| if (schema instanceof ComposedSchema composedSchema) { | ||
| composedSchema.getAnyOf().stream() | ||
| .filter(s -> s instanceof ObjectSchema) | ||
| .map(ObjectSchema.class::cast) | ||
| .forEach(s -> s.addProperty("@type", | ||
| new StringSchema() { | ||
| { | ||
| setType("string"); | ||
| setExample(t.name()); | ||
| } | ||
| })); | ||
| .map(ObjectSchema.class::cast).forEach(s -> { | ||
| StringSchema newProperty = new StringSchema(); | ||
| newProperty.setType("string"); | ||
| newProperty.setExample(t.name()); | ||
| s.addProperty(property, newProperty); | ||
| }); | ||
| } else if (schema instanceof ObjectSchema objectSchema) { | ||
| StringSchema newProperty = new StringSchema(); | ||
| newProperty.setType("string"); | ||
| newProperty.setExample(t.name()); | ||
| objectSchema.addProperty(property, newProperty); | ||
| } | ||
|
|
||
| }); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -100,16 +113,14 @@ public Collection<Class<? extends Plugin>> getRequiredPlugins() { | |
| @NonNull | ||
| @Override | ||
| public NodeDependencies scan(@NonNull NodeDependencies nodeDependencies) { | ||
| if (!(nodeDependencies.getNode() instanceof TypedNode)) { | ||
| if (!(nodeDependencies.getNode() instanceof TypedNode typedNode)) { | ||
| return nodeDependencies; | ||
| } | ||
|
|
||
| var typedNode = (TypedNode) nodeDependencies.getNode(); | ||
| if (!(typedNode.getType() instanceof ClassRefSignatureModel)) { | ||
| if (!(typedNode.getType() instanceof ClassRefSignatureModel ref)) { | ||
| return nodeDependencies; | ||
| } | ||
|
|
||
| var ref = (ClassRefSignatureModel) typedNode.getType(); | ||
| if (ref.isJDKClass() || ref.isDate() || ref.isIterable()) { | ||
| return nodeDependencies; | ||
| } | ||
|
|
@@ -118,7 +129,7 @@ public NodeDependencies scan(@NonNull NodeDependencies nodeDependencies) { | |
| // not used directly | ||
| Class<?> refClass = (Class<?>) ref.getClassInfo().get(); | ||
| var subTypes = getJsonSubTypes(refClass).map(JsonSubTypes.Type::value) | ||
| .map(ClassInfoModel::of).<Node<?, ?>> map(EntityNode::of); | ||
| .map(ClassInfoModel::of).<Node<?, ?>> map(EntityNode::of); | ||
|
|
||
| // create a union node for classes annotated with @JsonTypeInfo | ||
| if (refClass.getAnnotationsByType(JsonTypeInfo.class).length > 0) { | ||
|
|
@@ -131,19 +142,34 @@ public NodeDependencies scan(@NonNull NodeDependencies nodeDependencies) { | |
|
|
||
| private static Stream<JsonSubTypes.Type> getJsonSubTypes(Class<?> cls) { | ||
| return Optional.of(cls) | ||
| .map(c -> c.getAnnotationsByType(JsonSubTypes.class)) | ||
| .filter(a -> a.length > 0).map(a -> a[0]) | ||
| .map(JsonSubTypes::value).stream().flatMap(Arrays::stream); | ||
| .map(c -> c.getAnnotationsByType(JsonSubTypes.class)) | ||
| .filter(a -> a.length > 0).map(a -> a[0]).map(JsonSubTypes::value) | ||
| .stream().flatMap(Arrays::stream); | ||
| } | ||
|
|
||
| private static Optional<Pair<JsonTypeInfo, JsonSubTypes.Type[]>> getJsonSubTypeInHierarchy( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can use a record for better readability and to avoid using |
||
| Class<?> cls) { | ||
| Class<?> current = cls; | ||
| while (current != null) { | ||
| JsonTypeInfo typeInfo = current.getAnnotation(JsonTypeInfo.class); | ||
| JsonSubTypes types = current.getAnnotation(JsonSubTypes.class); | ||
| if (typeInfo != null && types != null) { | ||
| return Optional.of(Pair.of(typeInfo, types.value())); | ||
| } | ||
| current = current.getSuperclass(); | ||
| } | ||
|
|
||
| return Optional.empty(); | ||
| } | ||
|
|
||
| /** | ||
| * A node that represents the union of all the mentioned subclasses of a | ||
| * class annotated with {@code @JsonSubTypes}. | ||
| */ | ||
| public static class UnionNode | ||
| extends AbstractNode<ClassInfoModel, Schema<?>> { | ||
| extends AbstractNode<ClassInfoModel, Schema<?>> { | ||
| private UnionNode(@NonNull ClassInfoModel source, | ||
| @NonNull ObjectSchema target) { | ||
| @NonNull ObjectSchema target) { | ||
| super(source, target); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package com.vaadin.hilla.parser.plugins.subtypes; | ||
|
|
||
| public class AdvancedAddEvent extends AddEvent { | ||
| private Boolean defer; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Private fields are not generated, so this field is probably useless in the test. You could make it |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lang3is not a direct dependency in Hilla. If possible, avoid using it.