Skip to content

Commit

Permalink
Merge pull request quarkusio#44501 from Sgitario/44452
Browse files Browse the repository at this point in the history
Support title and type fields when generating HAL links
  • Loading branch information
geoand authored Nov 14, 2024
2 parents 309646d + 679c1ed commit aeeb628
Show file tree
Hide file tree
Showing 12 changed files with 117 additions and 13 deletions.
14 changes: 13 additions & 1 deletion extensions/hal/runtime/src/main/java/io/quarkus/hal/HalLink.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@
public class HalLink {

private final String href;
private final String title;
private final String type;

public HalLink(String href) {
public HalLink(String href, String title, String type) {
this.href = href;
this.title = title;
this.type = type;
}

public String getHref() {
return href;
}

public String getTitle() {
return title;
}

public String getType() {
return type;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ public class HalLinkJacksonSerializer extends JsonSerializer<HalLink> {
public void serialize(HalLink value, JsonGenerator generator, SerializerProvider serializers) throws IOException {
generator.writeStartObject();
generator.writeObjectField("href", value.getHref());
if (value.getTitle() != null) {
generator.writeObjectField("title", value.getTitle());
}

if (value.getType() != null) {
generator.writeObjectField("type", value.getType());
}

generator.writeEndObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ public class HalLinkJsonbSerializer implements JsonbSerializer<HalLink> {
public void serialize(HalLink value, JsonGenerator generator, SerializationContext context) {
generator.writeStartObject();
generator.write("href", value.getHref());
if (value.getTitle() != null) {
generator.write("title", value.getTitle());
}

if (value.getType() != null) {
generator.write("type", value.getType());
}

generator.writeEnd();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ public Map<String, HalLink> getLinks() {
@SuppressWarnings("unused")
public void addLinks(Link... links) {
for (Link link : links) {
this.links.put(link.getRel(), new HalLink(link.getUri().toString()));
this.links.put(link.getRel(), new HalLink(link.getUri().toString(),
link.getTitle(),
link.getType()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected Map<String, HalLink> getInstanceLinks(Object entity) {
private Map<String, HalLink> linksToMap(RESTServiceDiscovery serviceDiscovery) {
Map<String, HalLink> links = new HashMap<>(serviceDiscovery.size());
for (RESTServiceDiscovery.AtomLink atomLink : serviceDiscovery) {
links.put(atomLink.getRel(), new HalLink(atomLink.getHref()));
links.put(atomLink.getRel(), new HalLink(atomLink.getHref(), atomLink.getTitle(), atomLink.getType()));
}
return links;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,16 @@ private LinkInfo getLinkInfo(ResourceMethod resourceMethod, MethodInfo resourceM
AnnotationInstance restLinkAnnotation, String resourceClassPath, IndexView index) {
Type returnType = getNonAsyncReturnType(resourceMethodInfo.returnType());
String rel = getAnnotationValue(restLinkAnnotation, "rel", deductRel(resourceMethod, returnType, index));
String title = getAnnotationValue(restLinkAnnotation, "title", null);
String type = getAnnotationValue(restLinkAnnotation, "type", null);
String entityType = getAnnotationValue(restLinkAnnotation, "entityType", deductEntityType(returnType));
String path = UriBuilder.fromPath(resourceClassPath).path(resourceMethod.getPath()).toTemplate();
while (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
Set<String> pathParameters = getPathParameters(path);

return new LinkInfo(rel, entityType, path, pathParameters);
return new LinkInfo(rel, title, type, entityType, path, pathParameters);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import static io.restassured.RestAssured.given;
import static org.assertj.core.api.Assertions.assertThat;

import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.common.util.RestMediaType;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -89,5 +92,19 @@ void shouldGetHalLinksForRestLinkId() {
.thenReturn();

assertThat(response.body().jsonPath().getString("_links.self.href")).endsWith("/records/with-rest-link-id/100");
assertThat(response.body().jsonPath().getString("_links.self.title")).isEqualTo("The with rest link title");
assertThat(response.body().jsonPath().getString("_links.self.type")).isEqualTo(MediaType.APPLICATION_JSON);
}

@Test
void shouldIncludeAllFieldsFromLink() {
Response response = given()
.header(HttpHeaders.ACCEPT, RestMediaType.APPLICATION_HAL_JSON)
.get("/records/with-rest-link-with-all-fields")
.thenReturn();

assertThat(response.body().jsonPath().getString("_links.all.href")).endsWith("/records/with-rest-link-id/100");
assertThat(response.body().jsonPath().getString("_links.all.title")).isEqualTo("The title link");
assertThat(response.body().jsonPath().getString("_links.all.type")).isEqualTo(MediaType.APPLICATION_JSON);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.resteasy.reactive.links.deployment;

import java.net.URI;
import java.time.Duration;
import java.util.Arrays;
import java.util.LinkedList;
Expand All @@ -11,10 +12,12 @@
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Link;
import jakarta.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.common.util.RestMediaType;

import io.quarkus.hal.HalEntityWrapper;
import io.quarkus.resteasy.reactive.links.InjectRestLinks;
import io.quarkus.resteasy.reactive.links.RestLink;
import io.quarkus.resteasy.reactive.links.RestLinkType;
Expand Down Expand Up @@ -172,7 +175,7 @@ public TestRecordWithPersistenceId getWithPersistenceId(@PathParam("id") int id)
@GET
@Path("/with-rest-link-id/{id}")
@Produces({ MediaType.APPLICATION_JSON, RestMediaType.APPLICATION_HAL_JSON })
@RestLink(entityType = TestRecordWithRestLinkId.class)
@RestLink(entityType = TestRecordWithRestLinkId.class, title = "The with rest link title", type = MediaType.APPLICATION_JSON)
@InjectRestLinks
public TestRecordWithRestLinkId getWithRestLinkId(@PathParam("id") int id) {
return REST_LINK_ID_RECORDS.stream()
Expand All @@ -181,4 +184,18 @@ public TestRecordWithRestLinkId getWithRestLinkId(@PathParam("id") int id) {
.orElseThrow(NotFoundException::new);
}

@GET
@Path("/with-rest-link-with-all-fields")
@Produces({ MediaType.APPLICATION_JSON, RestMediaType.APPLICATION_HAL_JSON })
public HalEntityWrapper<TestRecordWithIdAndPersistenceIdAndRestLinkId> getAllFieldsFromLink() {

var entity = new TestRecordWithIdAndPersistenceIdAndRestLinkId(1, 10, 100, "one");
return new HalEntityWrapper<>(entity,
Link.fromUri(URI.create("/records/with-rest-link-id/100"))
.rel("all")
.title("The title link")
.type(MediaType.APPLICATION_JSON)
.build());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@
*/
String rel() default "";

/**
* Intended for labelling the link with a human-readable identifier.
*
* @return the link title.
*/
String title() default "";

/**
* Hint to indicate the media type expected when dereferencing the target resource.
*
* @return the link expected media type.
*/
String type() default "";

/**
* Declares a link for the given type of resources.
* If not set, it will default to the returning type of the annotated method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ public final class LinkInfo {

private final String rel;

private final String title;

private final String type;

private final String entityType;

private final String path;

private final Set<String> pathParameters;

public LinkInfo(String rel, String entityType, String path, Set<String> pathParameters) {
public LinkInfo(String rel, String title, String type, String entityType, String path, Set<String> pathParameters) {
this.rel = rel;
this.title = title;
this.type = type;
this.entityType = entityType;
this.path = path;
this.pathParameters = pathParameters;
Expand All @@ -23,6 +29,14 @@ public String getRel() {
return rel;
}

public String getTitle() {
return title;
}

public String getType() {
return type;
}

public String getEntityType() {
return entityType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ public Collection<Link> getTypeLinks(Class<?> elementType) {
List<Link> links = new ArrayList<>(linkInfoList.size());
for (LinkInfo linkInfo : linkInfoList) {
if (linkInfo.getPathParameters().size() == 0) {
links.add(Link.fromUriBuilder(uriInfo.getBaseUriBuilder().path(linkInfo.getPath()))
.rel(linkInfo.getRel())
.build());
links.add(linkBuilderFor(linkInfo).build());
}
}
return links;
Expand All @@ -52,13 +50,25 @@ public <T> Collection<Link> getInstanceLinks(T instance) {
List<LinkInfo> linkInfoList = linksContainer.getForClass(instance.getClass());
List<Link> links = new ArrayList<>(linkInfoList.size());
for (LinkInfo linkInfo : linkInfoList) {
links.add(Link.fromUriBuilder(uriInfo.getBaseUriBuilder().path(linkInfo.getPath()))
.rel(linkInfo.getRel())
.build(getPathParameterValues(linkInfo, instance)));
links.add(linkBuilderFor(linkInfo).build(getPathParameterValues(linkInfo, instance)));
}
return links;
}

private Link.Builder linkBuilderFor(LinkInfo linkInfo) {
Link.Builder builder = Link.fromUriBuilder(uriInfo.getBaseUriBuilder().path(linkInfo.getPath()))
.rel(linkInfo.getRel());
if (linkInfo.getTitle() != null) {
builder.title(linkInfo.getTitle());
}

if (linkInfo.getType() != null) {
builder.type(linkInfo.getType());
}

return builder;
}

private Object[] getPathParameterValues(LinkInfo linkInfo, Object instance) {
List<Object> values = new ArrayList<>(linkInfo.getPathParameters().size());
for (String name : linkInfo.getPathParameters()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ protected Map<String, HalLink> getInstanceLinks(Object entity) {
private Map<String, HalLink> linksToMap(Collection<Link> refLinks) {
Map<String, HalLink> links = new HashMap<>();
for (Link link : refLinks) {
links.put(link.getRel(), new HalLink(link.getUri().toString()));
links.put(link.getRel(), new HalLink(link.getUri().toString(), link.getTitle(), link.getType()));
}

return links;
Expand Down

0 comments on commit aeeb628

Please sign in to comment.