Skip to content

Commit f73284d

Browse files
authored
feat(REST API): Added labels to all search results (#5385)
* Added labels to all search results. The # of labels is capped to avoid overly large REST responses * Fixed issues with labels on branches (branches do not have labels).
1 parent a5928c9 commit f73284d

21 files changed

+332
-8
lines changed

app/src/main/java/io/apicurio/registry/rest/RestConfig.java

+8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ public class RestConfig {
1414
@Info(category = "rest", description = "Max size of the artifact allowed to be downloaded from URL", availableSince = "2.2.6")
1515
int downloadMaxSize;
1616

17+
@ConfigProperty(name = "apicurio.rest.search-results.labels.max-size.bytes", defaultValue = "512")
18+
@Info(category = "rest", description = "Max size of the labels (in bytes) per item from within search results", availableSince = "3.0.3")
19+
int labelsInSearchResultsMaxSize;
20+
1721
@ConfigProperty(name = "apicurio.rest.artifact.download.ssl-validation.disabled", defaultValue = "false")
1822
@Info(category = "rest", description = "Skip SSL validation when downloading artifacts from URL", availableSince = "2.2.6")
1923
boolean downloadSkipSSLValidation;
@@ -42,6 +46,10 @@ public int getDownloadMaxSize() {
4246
return this.downloadMaxSize;
4347
}
4448

49+
public int getLabelsInSearchResultsMaxSize() {
50+
return this.labelsInSearchResultsMaxSize;
51+
}
52+
4553
public boolean getDownloadSkipSSLValidation() {
4654
return this.downloadSkipSSLValidation;
4755
}

app/src/main/java/io/apicurio/registry/rest/v3/V3ApiUtil.java

+3
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ public static ArtifactSearchResults dtoToSearchResults(ArtifactSearchResultsDto
144144
sa.setModifiedOn(artifact.getModifiedOn());
145145
sa.setName(artifact.getName());
146146
sa.setArtifactType(artifact.getArtifactType());
147+
sa.setLabels(artifact.getLabels());
147148
results.getArtifacts().add(sa);
148149
});
149150
return results;
@@ -161,6 +162,7 @@ public static GroupSearchResults dtoToSearchResults(GroupSearchResultsDto dto) {
161162
sg.setGroupId(group.getId());
162163
sg.setModifiedBy(group.getModifiedBy());
163164
sg.setModifiedOn(group.getModifiedOn());
165+
sg.setLabels(group.getLabels());
164166
results.getGroups().add(sg);
165167
});
166168
return results;
@@ -203,6 +205,7 @@ public static VersionSearchResults dtoToSearchResults(VersionSearchResultsDto dt
203205
sv.setName(version.getName());
204206
sv.setState(version.getState());
205207
sv.setArtifactType(version.getArtifactType());
208+
sv.setLabels(version.getLabels());
206209
results.getVersions().add(sv);
207210
});
208211
return results;

app/src/main/java/io/apicurio/registry/storage/dto/SearchedArtifactDto.java

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import lombok.ToString;
1010

1111
import java.util.Date;
12+
import java.util.Map;
1213

1314
@NoArgsConstructor
1415
@AllArgsConstructor
@@ -28,4 +29,6 @@ public class SearchedArtifactDto {
2829
private String artifactType;
2930
private Date modifiedOn;
3031
private String modifiedBy;
32+
private Map<String, String> labels;
33+
3134
}

app/src/main/java/io/apicurio/registry/storage/dto/SearchedGroupDto.java

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import lombok.*;
44

55
import java.util.Date;
6+
import java.util.Map;
67

78
@NoArgsConstructor
89
@AllArgsConstructor
@@ -19,4 +20,6 @@ public class SearchedGroupDto {
1920
private String owner;
2021
private Date modifiedOn;
2122
private String modifiedBy;
23+
private Map<String, String> labels;
24+
2225
}

app/src/main/java/io/apicurio/registry/storage/dto/SearchedVersionDto.java

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import lombok.ToString;
1111

1212
import java.util.Date;
13+
import java.util.Map;
1314

1415
@NoArgsConstructor
1516
@AllArgsConstructor
@@ -34,4 +35,6 @@ public class SearchedVersionDto {
3435
private long globalId;
3536
private long contentId;
3637
private int versionOrder;
38+
private Map<String, String> labels;
39+
3740
}

app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java

+57
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.apicurio.registry.model.GA;
2424
import io.apicurio.registry.model.GAV;
2525
import io.apicurio.registry.model.VersionId;
26+
import io.apicurio.registry.rest.RestConfig;
2627
import io.apicurio.registry.rules.compatibility.CompatibilityLevel;
2728
import io.apicurio.registry.rules.integrity.IntegrityLevel;
2829
import io.apicurio.registry.rules.validity.ValidityLevel;
@@ -219,6 +220,9 @@ public abstract class AbstractSqlRegistryStorage implements RegistryStorage {
219220
@Inject
220221
SemVerConfigProperties semVerConfigProps;
221222

223+
@Inject
224+
RestConfig restConfig;
225+
222226
@Inject
223227

224228
protected SqlStatements sqlStatements() {
@@ -1112,6 +1116,7 @@ public ArtifactSearchResultsDto searchArtifacts(Set<SearchFilter> filters, Order
11121116

11131117
// Execute artifact query
11141118
List<SearchedArtifactDto> artifacts = artifactsQuery.map(SearchedArtifactMapper.instance).list();
1119+
limitReturnedLabelsInArtifacts(artifacts);
11151120
// Execute count query
11161121
Integer count = countQuery.mapTo(Integer.class).one();
11171122

@@ -1736,6 +1741,7 @@ public VersionSearchResultsDto searchVersions(Set<SearchFilter> filters, OrderBy
17361741

17371742
// Execute query
17381743
List<SearchedVersionDto> versions = versionsQuery.map(SearchedVersionMapper.instance).list();
1744+
limitReturnedLabelsInVersions(versions);
17391745
// Execute count query
17401746
Integer count = countQuery.mapTo(Integer.class).one();
17411747

@@ -2907,6 +2913,8 @@ public GroupSearchResultsDto searchGroups(Set<SearchFilter> filters, OrderBy ord
29072913

29082914
// Execute query
29092915
List<SearchedGroupDto> groups = groupsQuery.map(SearchedGroupMapper.instance).list();
2916+
limitReturnedLabelsInGroups(groups);
2917+
29102918
// Execute count query
29112919
Integer count = countQuery.mapTo(Integer.class).one();
29122920

@@ -3539,6 +3547,7 @@ public VersionSearchResultsDto getBranchVersions(GA ga, BranchId branchId, int o
35393547

35403548
// Execute query
35413549
List<SearchedVersionDto> versions = versionsQuery.map(SearchedVersionMapper.instance).list();
3550+
limitReturnedLabelsInVersions(versions);
35423551
// Execute count query
35433552
Integer count = countQuery.mapTo(Integer.class).one();
35443553

@@ -3788,4 +3797,52 @@ private boolean isMssql() {
37883797
private boolean isH2() {
37893798
return sqlStatements.dbType().equals("h2");
37903799
}
3800+
3801+
/*
3802+
* Ensures that only a reasonable number/size of labels for each item in the list are returned. This is to
3803+
* guard against an unexpectedly enormous response size to a REST API search operation.
3804+
*/
3805+
3806+
private Map<String, String> limitReturnedLabels(Map<String, String> labels) {
3807+
int maxBytes = restConfig.getLabelsInSearchResultsMaxSize();
3808+
if (labels != null && !labels.isEmpty()) {
3809+
Map<String, String> cappedLabels = new HashMap<>();
3810+
int totalBytes = 0;
3811+
for (String key : labels.keySet()) {
3812+
if (totalBytes < maxBytes) {
3813+
String value = labels.get(key);
3814+
cappedLabels.put(key, value);
3815+
totalBytes += key.length() + (value != null ? value.length() : 0);
3816+
}
3817+
}
3818+
return cappedLabels;
3819+
}
3820+
3821+
return null;
3822+
}
3823+
3824+
private void limitReturnedLabelsInGroups(List<SearchedGroupDto> groups) {
3825+
groups.forEach(group -> {
3826+
Map<String, String> labels = group.getLabels();
3827+
Map<String, String> cappedLabels = limitReturnedLabels(labels);
3828+
group.setLabels(cappedLabels);
3829+
});
3830+
}
3831+
3832+
private void limitReturnedLabelsInArtifacts(List<SearchedArtifactDto> artifacts) {
3833+
artifacts.forEach(artifact -> {
3834+
Map<String, String> labels = artifact.getLabels();
3835+
Map<String, String> cappedLabels = limitReturnedLabels(labels);
3836+
artifact.setLabels(cappedLabels);
3837+
});
3838+
}
3839+
3840+
private void limitReturnedLabelsInVersions(List<SearchedVersionDto> versions) {
3841+
versions.forEach(version -> {
3842+
Map<String, String> labels = version.getLabels();
3843+
Map<String, String> cappedLabels = limitReturnedLabels(labels);
3844+
version.setLabels(cappedLabels);
3845+
});
3846+
}
3847+
37913848
}

app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/SearchedArtifactMapper.java

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public SearchedArtifactDto map(ResultSet rs) throws SQLException {
3232
dto.setModifiedBy(rs.getString("modifiedBy"));
3333
dto.setModifiedOn(rs.getTimestamp("modifiedOn"));
3434
dto.setArtifactType(rs.getString("type"));
35+
dto.setLabels(RegistryContentUtils.deserializeLabels(rs.getString("labels")));
3536
return dto;
3637
}
3738

app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/SearchedGroupMapper.java

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.apicurio.registry.storage.impl.sql.mappers;
22

33
import io.apicurio.registry.storage.dto.SearchedGroupDto;
4+
import io.apicurio.registry.storage.impl.sql.RegistryContentUtils;
45
import io.apicurio.registry.storage.impl.sql.jdb.RowMapper;
56

67
import java.sql.ResultSet;
@@ -28,6 +29,7 @@ public SearchedGroupDto map(ResultSet rs) throws SQLException {
2829
dto.setDescription(rs.getString("description"));
2930
dto.setModifiedBy(rs.getString("modifiedBy"));
3031
dto.setModifiedOn(rs.getTimestamp("modifiedOn"));
32+
dto.setLabels(RegistryContentUtils.deserializeLabels(rs.getString("labels")));
3133
return dto;
3234
}
3335
}

app/src/main/java/io/apicurio/registry/storage/impl/sql/mappers/SearchedVersionMapper.java

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public SearchedVersionDto map(ResultSet rs) throws SQLException {
3838
dto.setName(rs.getString("name"));
3939
dto.setDescription(rs.getString("description"));
4040
dto.setArtifactType(rs.getString("type"));
41+
dto.setLabels(RegistryContentUtils.deserializeLabels(rs.getString("labels")));
4142
return dto;
4243
}
4344

app/src/main/resources/application.properties

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ apicurio.redirects.root=/,/apis
150150
apicurio.rest.artifact.download.max-size.bytes=1000000
151151
apicurio.rest.artifact.download.ssl-validation.disabled=false
152152
apicurio.rest.artifact.deletion.enabled=false
153+
apicurio.rest.search-results.labels.max-size.bytes=512
153154
# Api date format
154155
apicurio.apis.date-format=yyyy-MM-dd'T'HH:mm:ss'Z'
155156
apicurio.apis.date-format-timezone=UTC
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.apicurio.registry.noprofile.rest.v3;
2+
3+
import io.apicurio.registry.AbstractResourceTestBase;
4+
import io.apicurio.registry.rest.client.models.CreateGroup;
5+
import io.apicurio.registry.rest.client.models.GroupMetaData;
6+
import io.apicurio.registry.rest.client.models.GroupSearchResults;
7+
import io.apicurio.registry.rest.client.models.Labels;
8+
import io.apicurio.registry.utils.tests.TestUtils;
9+
import io.quarkus.test.junit.QuarkusTest;
10+
import org.junit.jupiter.api.Assertions;
11+
import org.junit.jupiter.api.Test;
12+
13+
import java.util.HashMap;
14+
15+
@QuarkusTest
16+
public class CappedLabelsInSearchResultsTest extends AbstractResourceTestBase {
17+
18+
@Test
19+
public void testCappedLabelsInGroupSearch() throws Exception {
20+
String groupId = TestUtils.generateGroupId();
21+
22+
// Lots of labels! Too many labels.
23+
Labels labels = new Labels();
24+
labels.setAdditionalData(new HashMap<>());
25+
for (int idx = 1000; idx < 1500; idx++) {
26+
labels.getAdditionalData().put("test-key-" + idx, "test-value-" + idx);
27+
}
28+
29+
// Create a group with all these labels.
30+
CreateGroup createGroup = new CreateGroup();
31+
createGroup.setGroupId(groupId);
32+
createGroup.setLabels(labels);
33+
clientV3.groups().post(createGroup);
34+
35+
// Get the group meta data
36+
GroupMetaData gmd = clientV3.groups().byGroupId(groupId).get();
37+
Assertions.assertNotNull(gmd);
38+
Assertions.assertEquals(groupId, gmd.getGroupId());
39+
Assertions.assertNotNull(gmd.getLabels());
40+
Assertions.assertEquals(500, gmd.getLabels().getAdditionalData().size());
41+
42+
// Search for the group.
43+
GroupSearchResults results = clientV3.search().groups().get(request -> {
44+
request.queryParameters.groupId = groupId;
45+
});
46+
Assertions.assertEquals(1, results.getGroups().size());
47+
48+
Assertions.assertNotNull(results.getGroups().get(0).getLabels());
49+
// Only 19 labels are returned due to the cap/limit enforced on returning labels in searches
50+
Assertions.assertEquals(19, results.getGroups().get(0).getLabels().getAdditionalData().size());
51+
}
52+
}

app/src/test/java/io/apicurio/registry/noprofile/rest/v3/SearchArtifactsTest.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package io.apicurio.registry.noprofile.rest.v3;
22

33
import io.apicurio.registry.AbstractResourceTestBase;
4+
import io.apicurio.registry.rest.client.models.ArtifactSearchResults;
45
import io.apicurio.registry.rest.v3.beans.EditableArtifactMetaData;
56
import io.apicurio.registry.types.ArtifactType;
67
import io.apicurio.registry.types.ContentTypes;
8+
import io.apicurio.registry.utils.tests.TestUtils;
79
import io.quarkus.test.junit.QuarkusTest;
10+
import org.junit.jupiter.api.Assertions;
811
import org.junit.jupiter.api.Test;
912

1013
import java.util.HashMap;
@@ -95,7 +98,7 @@ public void testSearchArtifactsByDescription() throws Exception {
9598

9699
@Test
97100
public void testSearchArtifactsByLabels() throws Exception {
98-
String group = UUID.randomUUID().toString();
101+
String group = TestUtils.generateGroupId();
99102
String artifactContent = resourceToString("openapi-empty.json");
100103

101104
// Create 5 artifacts with various labels
@@ -152,6 +155,19 @@ public void testSearchArtifactsByLabels() throws Exception {
152155
.statusCode(400);
153156
given().when().queryParam("labels", "all-key:").get("/registry/v3/search/artifacts").then()
154157
.statusCode(400);
158+
159+
// Test that search results contain the labels
160+
ArtifactSearchResults results = clientV3.search().artifacts().get(conf -> {
161+
conf.queryParameters.groupId = group;
162+
conf.queryParameters.labels = new String[] { "key-1:value-1" };
163+
});
164+
Assertions.assertNotNull(results);
165+
Assertions.assertEquals(1, results.getArtifacts().size());
166+
Assertions.assertNotNull(results.getArtifacts().get(0).getLabels());
167+
Assertions.assertEquals(
168+
Map.of("key-1", "value-1", "another-key-1", "another-value-1", "all-key", "all-value",
169+
"a-key-1", "lorem ipsum", "extra-key-1", "lorem ipsum"),
170+
results.getArtifacts().get(0).getLabels().getAdditionalData());
155171
}
156172

157173
@Test

app/src/test/java/io/apicurio/registry/noprofile/rest/v3/SearchGroupsTest.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ public void testSearchGroupsByLabels() throws Exception {
7979
request.queryParameters.groupId = groupId + "1";
8080
});
8181
Assertions.assertEquals(1, results.getGroups().size());
82+
// Note: ensure that labels are returned in the search results
83+
Assertions.assertNotNull(results.getGroups().get(0).getLabels());
84+
Assertions.assertEquals(Map.of("byLabels", "byLabels-value-1", "byLabels-1", "byLabels-value-1"),
85+
results.getGroups().get(0).getLabels().getAdditionalData());
8286

8387
results = clientV3.search().groups().get(request -> {
8488
request.queryParameters.labels = new String[] { "byLabels" };
@@ -109,5 +113,4 @@ public void testSearchGroupsByLabels() throws Exception {
109113
Assertions.assertEquals(1, results.getGroups().size());
110114
Assertions.assertEquals("testSearchGroupsByLabels3", results.getGroups().get(0).getGroupId());
111115
}
112-
113116
}

0 commit comments

Comments
 (0)