Skip to content

Commit 7f88710

Browse files
feat(openapi): precondition exceptions return 412 (#12552)
1 parent 23a86fd commit 7f88710

File tree

8 files changed

+384
-37
lines changed

8 files changed

+384
-37
lines changed

entity-registry/src/main/java/com/linkedin/metadata/aspect/plugins/validation/AspectValidationException.java

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,53 +18,47 @@ public static AspectValidationException forItem(BatchItem item, String msg) {
1818
}
1919

2020
public static AspectValidationException forItem(BatchItem item, String msg, Exception e) {
21-
return new AspectValidationException(item, msg, SubType.VALIDATION, e);
21+
return new AspectValidationException(item, msg, ValidationSubType.VALIDATION, e);
2222
}
2323

2424
public static AspectValidationException forPrecondition(BatchItem item, String msg) {
2525
return forPrecondition(item, msg, null);
2626
}
2727

2828
public static AspectValidationException forFilter(BatchItem item, String msg) {
29-
return new AspectValidationException(item, msg, SubType.FILTER);
29+
return new AspectValidationException(item, msg, ValidationSubType.FILTER);
3030
}
3131

3232
public static AspectValidationException forPrecondition(BatchItem item, String msg, Exception e) {
33-
return new AspectValidationException(item, msg, SubType.PRECONDITION, e);
33+
return new AspectValidationException(item, msg, ValidationSubType.PRECONDITION, e);
3434
}
3535

3636
@Nonnull BatchItem item;
3737
@Nonnull ChangeType changeType;
3838
@Nonnull Urn entityUrn;
3939
@Nonnull String aspectName;
40-
@Nonnull SubType subType;
40+
@Nonnull ValidationSubType subType;
4141
@Nullable String msg;
4242

43-
public AspectValidationException(@Nonnull BatchItem item, String msg, SubType subType) {
43+
public AspectValidationException(@Nonnull BatchItem item, String msg, ValidationSubType subType) {
4444
this(item, msg, subType, null);
4545
}
4646

4747
public AspectValidationException(
48-
@Nonnull BatchItem item, @Nonnull String msg, @Nullable SubType subType, Exception e) {
48+
@Nonnull BatchItem item,
49+
@Nonnull String msg,
50+
@Nullable ValidationSubType subType,
51+
Exception e) {
4952
super(msg, e);
5053
this.item = item;
5154
this.changeType = item.getChangeType();
5255
this.entityUrn = item.getUrn();
5356
this.aspectName = item.getAspectName();
5457
this.msg = msg;
55-
this.subType = subType != null ? subType : SubType.VALIDATION;
58+
this.subType = subType != null ? subType : ValidationSubType.VALIDATION;
5659
}
5760

5861
public Pair<Urn, String> getAspectGroup() {
5962
return Pair.of(entityUrn, aspectName);
6063
}
61-
62-
public enum SubType {
63-
// A validation exception is thrown
64-
VALIDATION,
65-
// A failed precondition is thrown if the header constraints are not met
66-
PRECONDITION,
67-
// Exclude from processing further
68-
FILTER
69-
}
7064
}

entity-registry/src/main/java/com/linkedin/metadata/aspect/plugins/validation/ValidationExceptionCollection.java

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.util.Comparator;
88
import java.util.HashMap;
99
import java.util.HashSet;
10+
import java.util.Map;
1011
import java.util.Set;
1112
import java.util.stream.Collectors;
1213
import java.util.stream.Stream;
@@ -15,17 +16,20 @@
1516
public class ValidationExceptionCollection
1617
extends HashMap<Pair<Urn, String>, Set<AspectValidationException>> {
1718

18-
private final Set<Integer> failedHashCodes;
19-
private final Set<Integer> filteredHashCodes;
19+
private final Map<ValidationSubType, Set<Integer>> subTypeHashCodes;
2020

2121
public ValidationExceptionCollection() {
2222
super();
23-
this.failedHashCodes = new HashSet<>();
24-
this.filteredHashCodes = new HashSet<>();
23+
this.subTypeHashCodes = new HashMap<>();
2524
}
2625

2726
public boolean hasFatalExceptions() {
28-
return !failedHashCodes.isEmpty();
27+
return subTypeHashCodes.keySet().stream()
28+
.anyMatch(subType -> !ValidationSubType.FILTER.equals(subType));
29+
}
30+
31+
public Set<ValidationSubType> getSubTypes() {
32+
return subTypeHashCodes.keySet();
2933
}
3034

3135
public static ValidationExceptionCollection newCollection() {
@@ -34,11 +38,9 @@ public static ValidationExceptionCollection newCollection() {
3438

3539
public void addException(AspectValidationException exception) {
3640
super.computeIfAbsent(exception.getAspectGroup(), key -> new HashSet<>()).add(exception);
37-
if (!AspectValidationException.SubType.FILTER.equals(exception.getSubType())) {
38-
failedHashCodes.add(exception.getItem().hashCode());
39-
} else {
40-
filteredHashCodes.add(exception.getItem().hashCode());
41-
}
41+
subTypeHashCodes
42+
.computeIfAbsent(exception.getSubType(), key -> new HashSet<>())
43+
.add(exception.getItem().hashCode());
4244
}
4345

4446
public void addException(BatchItem item, String message) {
@@ -58,16 +60,27 @@ public <T extends BatchItem> Collection<T> successful(Collection<T> items) {
5860
}
5961

6062
public <T extends BatchItem> Stream<T> streamSuccessful(Stream<T> items) {
61-
return items.filter(
62-
i -> !failedHashCodes.contains(i.hashCode()) && !filteredHashCodes.contains(i.hashCode()));
63+
return items.filter(i -> isSuccessful(i.hashCode()));
6364
}
6465

6566
public <T extends BatchItem> Collection<T> exceptions(Collection<T> items) {
6667
return streamExceptions(items.stream()).collect(Collectors.toList());
6768
}
6869

6970
public <T extends BatchItem> Stream<T> streamExceptions(Stream<T> items) {
70-
return items.filter(i -> failedHashCodes.contains(i.hashCode()));
71+
return items.filter(i -> isException(i.hashCode()));
72+
}
73+
74+
private boolean isException(int hashCode) {
75+
return subTypeHashCodes.keySet().stream()
76+
.filter(subType -> !ValidationSubType.FILTER.equals(subType))
77+
.anyMatch(subType -> subTypeHashCodes.get(subType).contains(hashCode));
78+
}
79+
80+
private boolean isSuccessful(int hashCode) {
81+
return !isException(hashCode)
82+
&& (!subTypeHashCodes.containsKey(ValidationSubType.FILTER)
83+
|| !subTypeHashCodes.get(ValidationSubType.FILTER).contains(hashCode));
7184
}
7285

7386
@Override
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.linkedin.metadata.aspect.plugins.validation;
2+
3+
public enum ValidationSubType {
4+
// A validation exception is thrown
5+
VALIDATION,
6+
// A failed precondition is thrown if the header constraints are not met
7+
PRECONDITION,
8+
// Exclude from processing further
9+
FILTER
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
package com.linkedin.metadata.aspect.plugins.validation;
2+
3+
import static org.mockito.Mockito.*;
4+
import static org.testng.Assert.*;
5+
6+
import com.datahub.test.TestEntityProfile;
7+
import com.linkedin.common.Status;
8+
import com.linkedin.common.urn.Urn;
9+
import com.linkedin.common.urn.UrnUtils;
10+
import com.linkedin.data.schema.annotation.PathSpecBasedSchemaAnnotationVisitor;
11+
import com.linkedin.metadata.aspect.batch.BatchItem;
12+
import com.linkedin.metadata.models.registry.ConfigEntityRegistry;
13+
import com.linkedin.metadata.models.registry.EntityRegistry;
14+
import com.linkedin.test.metadata.aspect.batch.TestMCP;
15+
import java.util.Arrays;
16+
import java.util.Collection;
17+
import java.util.List;
18+
import java.util.Set;
19+
import org.testng.annotations.BeforeMethod;
20+
import org.testng.annotations.BeforeTest;
21+
import org.testng.annotations.Test;
22+
23+
public class ValidationExceptionCollectionTest {
24+
private final Urn TEST_URN = UrnUtils.getUrn("urn:li:chart:123");
25+
26+
private ValidationExceptionCollection collection;
27+
private EntityRegistry testEntityRegistry;
28+
29+
private static final String ERROR_MESSAGE = "Test error message";
30+
31+
@BeforeTest
32+
public void disableAssert() {
33+
PathSpecBasedSchemaAnnotationVisitor.class
34+
.getClassLoader()
35+
.setClassAssertionStatus(PathSpecBasedSchemaAnnotationVisitor.class.getName(), false);
36+
}
37+
38+
@BeforeMethod
39+
public void setUp() {
40+
collection = ValidationExceptionCollection.newCollection();
41+
testEntityRegistry =
42+
new ConfigEntityRegistry(
43+
TestEntityProfile.class
44+
.getClassLoader()
45+
.getResourceAsStream("test-entity-registry.yml"));
46+
}
47+
48+
@Test
49+
public void testNewCollection() {
50+
assertNotNull(collection);
51+
assertTrue(collection.isEmpty());
52+
assertFalse(collection.hasFatalExceptions());
53+
assertEquals(collection.getSubTypes().size(), 0);
54+
}
55+
56+
@Test
57+
public void testAddException() {
58+
BatchItem testItem =
59+
TestMCP.ofOneMCP(TEST_URN, new Status(), testEntityRegistry).stream().findFirst().get();
60+
AspectValidationException exception =
61+
new AspectValidationException(testItem, ERROR_MESSAGE, ValidationSubType.VALIDATION, null);
62+
63+
collection.addException(exception);
64+
65+
assertEquals(collection.size(), 1);
66+
assertTrue(collection.containsKey(exception.getAspectGroup()));
67+
assertTrue(collection.get(exception.getAspectGroup()).contains(exception));
68+
}
69+
70+
@Test
71+
public void testAddExceptionWithMessage() {
72+
BatchItem testItem =
73+
TestMCP.ofOneMCP(TEST_URN, new Status(), testEntityRegistry).stream().findFirst().get();
74+
collection.addException(testItem, ERROR_MESSAGE);
75+
76+
assertEquals(collection.size(), 1);
77+
assertTrue(collection.hasFatalExceptions());
78+
}
79+
80+
@Test
81+
public void testHasFatalExceptionsWithMultipleTypes() {
82+
BatchItem testItem =
83+
TestMCP.ofOneMCP(TEST_URN, new Status(), testEntityRegistry).stream().findFirst().get();
84+
85+
// Add FILTER exception
86+
collection.addException(
87+
new AspectValidationException(testItem, ERROR_MESSAGE, ValidationSubType.FILTER, null));
88+
assertFalse(collection.hasFatalExceptions());
89+
90+
// Add VALIDATION exception
91+
collection.addException(
92+
new AspectValidationException(testItem, ERROR_MESSAGE, ValidationSubType.VALIDATION, null));
93+
assertTrue(collection.hasFatalExceptions());
94+
}
95+
96+
@Test
97+
public void testGetSubTypesWithAllTypes() {
98+
BatchItem testItem =
99+
TestMCP.ofOneMCP(TEST_URN, new Status(), testEntityRegistry).stream().findFirst().get();
100+
101+
collection.addException(
102+
new AspectValidationException(testItem, ERROR_MESSAGE, ValidationSubType.FILTER, null));
103+
collection.addException(
104+
new AspectValidationException(testItem, ERROR_MESSAGE, ValidationSubType.VALIDATION, null));
105+
collection.addException(
106+
new AspectValidationException(
107+
testItem, ERROR_MESSAGE, ValidationSubType.PRECONDITION, null));
108+
109+
Set<ValidationSubType> subTypes = collection.getSubTypes();
110+
assertEquals(subTypes.size(), 3);
111+
assertTrue(
112+
subTypes.containsAll(
113+
Arrays.asList(
114+
ValidationSubType.FILTER,
115+
ValidationSubType.VALIDATION,
116+
ValidationSubType.PRECONDITION)));
117+
}
118+
119+
@Test
120+
public void testSuccessfulAndExceptionItems() {
121+
BatchItem validationItem =
122+
TestMCP.ofOneMCP(UrnUtils.getUrn("urn:li:chart:111"), new Status(), testEntityRegistry)
123+
.stream()
124+
.findFirst()
125+
.get();
126+
BatchItem filterItem =
127+
TestMCP.ofOneMCP(UrnUtils.getUrn("urn:li:chart:222"), new Status(), testEntityRegistry)
128+
.stream()
129+
.findFirst()
130+
.get();
131+
BatchItem successItem =
132+
TestMCP.ofOneMCP(UrnUtils.getUrn("urn:li:chart:333"), new Status(), testEntityRegistry)
133+
.stream()
134+
.findFirst()
135+
.get();
136+
137+
collection.addException(
138+
new AspectValidationException(
139+
validationItem, ERROR_MESSAGE, ValidationSubType.VALIDATION, null));
140+
collection.addException(
141+
new AspectValidationException(filterItem, ERROR_MESSAGE, ValidationSubType.FILTER, null));
142+
143+
Collection<BatchItem> items = Arrays.asList(validationItem, filterItem, successItem);
144+
145+
// Test successful items
146+
Collection<BatchItem> successful = collection.successful(items);
147+
assertEquals(successful.size(), 1);
148+
assertTrue(successful.contains(successItem));
149+
150+
// Test exception items
151+
Collection<BatchItem> exceptions = collection.exceptions(items);
152+
assertEquals(exceptions.size(), 1);
153+
assertTrue(exceptions.contains(validationItem));
154+
assertFalse(exceptions.contains(filterItem)); // FILTER type should not be included
155+
}
156+
157+
@Test
158+
public void testStreamOperations() {
159+
BatchItem validationItem =
160+
TestMCP.ofOneMCP(UrnUtils.getUrn("urn:li:chart:111"), new Status(), testEntityRegistry)
161+
.stream()
162+
.findFirst()
163+
.get();
164+
BatchItem successItem =
165+
TestMCP.ofOneMCP(UrnUtils.getUrn("urn:li:chart:222"), new Status(), testEntityRegistry)
166+
.stream()
167+
.findFirst()
168+
.get();
169+
170+
collection.addException(
171+
new AspectValidationException(
172+
validationItem, ERROR_MESSAGE, ValidationSubType.VALIDATION, null));
173+
174+
List<BatchItem> items = Arrays.asList(validationItem, successItem);
175+
176+
// Test streamSuccessful
177+
List<BatchItem> successful = collection.streamSuccessful(items.stream()).toList();
178+
assertEquals(successful.size(), 1);
179+
assertTrue(successful.contains(successItem));
180+
181+
// Test streamExceptions
182+
List<BatchItem> exceptions = collection.streamExceptions(items.stream()).toList();
183+
assertEquals(exceptions.size(), 1);
184+
assertTrue(exceptions.contains(validationItem));
185+
}
186+
187+
@Test
188+
public void testMultipleExceptionsForSameEntityDifferentAspects() {
189+
BatchItem item1 =
190+
TestMCP.ofOneMCP(UrnUtils.getUrn("urn:li:chart:111"), new Status(), testEntityRegistry)
191+
.stream()
192+
.findFirst()
193+
.get();
194+
BatchItem item2 =
195+
TestMCP.ofOneMCP(UrnUtils.getUrn("urn:li:chart:222"), new Status(), testEntityRegistry)
196+
.stream()
197+
.findFirst()
198+
.get();
199+
200+
collection.addException(
201+
new AspectValidationException(item1, ERROR_MESSAGE, ValidationSubType.VALIDATION, null));
202+
collection.addException(
203+
new AspectValidationException(item2, ERROR_MESSAGE, ValidationSubType.VALIDATION, null));
204+
205+
assertEquals(collection.size(), 2);
206+
assertEquals(collection.getSubTypes().size(), 1);
207+
}
208+
209+
@Test
210+
public void testToString() {
211+
BatchItem testItem =
212+
TestMCP.ofOneMCP(TEST_URN, new Status(), testEntityRegistry).stream().findFirst().get();
213+
AspectValidationException exception =
214+
new AspectValidationException(testItem, ERROR_MESSAGE, ValidationSubType.VALIDATION, null);
215+
216+
collection.addException(exception);
217+
218+
String result = collection.toString();
219+
assertTrue(result.contains("ValidationExceptionCollection"));
220+
assertTrue(result.contains("EntityAspect:"));
221+
assertTrue(result.contains("urn:li:chart:123"));
222+
}
223+
}

0 commit comments

Comments
 (0)