Skip to content

Commit 1c98ebd

Browse files
Merge pull request #151 from xenit-eu/ACC-2387
[ACC-2387] Ensure that _embedded.item is always present for collections
2 parents 10d41b7 + 6465549 commit 1c98ebd

File tree

2 files changed

+54
-2
lines changed

2 files changed

+54
-2
lines changed

contentgrid-appserver-rest/src/main/java/com/contentgrid/appserver/rest/assembler/EntityDataRepresentationModelAssembler.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,20 @@
1818
import com.contentgrid.appserver.rest.links.ContentGridLinkRelations;
1919
import com.contentgrid.hateoas.spring.pagination.SlicedResourcesAssembler;
2020
import com.contentgrid.hateoas.spring.server.RepresentationModelContextAssembler;
21+
import java.util.Collection;
22+
import java.util.List;
2123
import java.util.Map;
2224
import java.util.Optional;
2325
import lombok.RequiredArgsConstructor;
2426
import lombok.With;
2527
import org.springframework.hateoas.CollectionModel;
2628
import org.springframework.hateoas.IanaLinkRelations;
2729
import org.springframework.hateoas.Link;
30+
import org.springframework.hateoas.LinkRelation;
2831
import org.springframework.hateoas.PagedModel;
2932
import org.springframework.hateoas.PagedModel.PageMetadata;
3033
import org.springframework.hateoas.server.RepresentationModelAssembler;
34+
import org.springframework.hateoas.server.core.EmbeddedWrapper;
3135
import org.springframework.http.HttpMethod;
3236
import org.springframework.lang.NonNull;
3337
import org.springframework.stereotype.Component;
@@ -76,7 +80,13 @@ public CollectionModel<EntityDataRepresentationModel> toSlicedModel(ResultSlice
7680
var pageMetadata = getPageMetadata(slice);
7781

7882
// Add pageMetadata to slicedModel by wrapping it in a PagedModel
79-
return PagedModel.of(slicedModel.getContent(), pageMetadata, slicedModel.getLinks());
83+
return PagedModel.of(wrap(slicedModel.getContent()), pageMetadata, slicedModel.getLinks());
84+
}
85+
86+
private Collection<EntityDataRepresentationModel> wrap(Collection<EntityDataRepresentationModel> content) {
87+
// Yes, this is casting to a type that it really isn't, but that's the only way to make spring hateoas
88+
// properly make this object always present and have an 'item' linkrel
89+
return (Collection)List.of(new EntityDataCollectionRepresentationModelEmbeddedWrapper(content));
8090
}
8191

8292
@Override
@@ -85,7 +95,8 @@ public CollectionModel<EntityDataRepresentationModel> toCollectionModel(Iterable
8595
if (entities instanceof ResultSlice slice) {
8696
return toSlicedModel(slice, context);
8797
}
88-
var result = RepresentationModelContextAssembler.super.toCollectionModel(entities, context);
98+
var result = RepresentationModelContextAssembler.super.toCollectionModel(entities, context)
99+
.withFallbackType(EntityDataRepresentationModel.class);
89100
result.add(getCollectionSelfLink(context))
90101
.add(getEntityProfileLink(context));
91102
return result;
@@ -152,4 +163,35 @@ HalFormsTemplateGenerator templateGenerator() {
152163
return new HalFormsTemplateGenerator(application, userLocales, linkFactoryProvider);
153164
}
154165
}
166+
167+
@RequiredArgsConstructor
168+
private static class EntityDataCollectionRepresentationModelEmbeddedWrapper implements EmbeddedWrapper {
169+
170+
private final Collection<EntityDataRepresentationModel> contents;
171+
172+
@Override
173+
public Optional<LinkRelation> getRel() {
174+
return Optional.of(IanaLinkRelations.ITEM);
175+
}
176+
177+
@Override
178+
public boolean hasRel(LinkRelation rel) {
179+
return IanaLinkRelations.ITEM.isSameAs(rel);
180+
}
181+
182+
@Override
183+
public boolean isCollectionValue() {
184+
return true;
185+
}
186+
187+
@Override
188+
public Object getValue() {
189+
return contents;
190+
}
191+
192+
@Override
193+
public Class<?> getRelTargetType() {
194+
return EntityDataRepresentationModel.class;
195+
}
196+
}
155197
}

contentgrid-appserver-rest/src/test/java/com/contentgrid/appserver/rest/EntityRestControllerTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static com.contentgrid.appserver.application.model.fixtures.ModelTestFixtures.*;
44
import static org.assertj.core.api.Assertions.assertThat;
55
import static org.hamcrest.Matchers.containsString;
6+
import static org.hamcrest.Matchers.emptyArray;
67
import static org.hamcrest.Matchers.is;
78
import static org.hamcrest.Matchers.notNullValue;
89
import static org.hamcrest.Matchers.nullValue;
@@ -686,6 +687,15 @@ void testListEntityInstances() throws Exception {
686687
.andExpect(jsonPath("$.page").exists());
687688
}
688689

690+
@Test
691+
void testListEntityInstances_emptyResults() throws Exception {
692+
mockMvc.perform(get("/products").accept(MediaTypes.HAL_JSON))
693+
.andExpect(status().isOk())
694+
.andExpect(content().contentType(MediaTypes.HAL_JSON))
695+
.andExpect(jsonPath("$._embedded.item").isArray())
696+
.andExpect(jsonPath("$._embedded.item[0]").doesNotExist());
697+
}
698+
689699
@Test
690700
void testListEntityInstances_withSorting() throws Exception {
691701
// Create entity with less-sorting price but greater-sorting name

0 commit comments

Comments
 (0)