-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: supports combining custom matchers for field selector
- Loading branch information
Showing
10 changed files
with
308 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,4 +10,4 @@ | |
public class ListOptions { | ||
private LabelSelector labelSelector; | ||
private FieldSelector fieldSelector; | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
api/src/main/java/run/halo/app/extension/router/selector/AndSelectorMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package run.halo.app.extension.router.selector; | ||
|
||
import lombok.Getter; | ||
|
||
@Getter | ||
public class AndSelectorMatcher extends LogicalMatcher { | ||
private final SelectorMatcher left; | ||
private final SelectorMatcher right; | ||
|
||
public AndSelectorMatcher(SelectorMatcher left, SelectorMatcher right) { | ||
this.left = left; | ||
this.right = right; | ||
} | ||
|
||
public boolean test(String s) { | ||
return left.test(s) && right.test(s); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
api/src/main/java/run/halo/app/extension/router/selector/AnySelectorMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package run.halo.app.extension.router.selector; | ||
|
||
public class AnySelectorMatcher extends LogicalMatcher { | ||
|
||
@Override | ||
public boolean test(String s) { | ||
return true; | ||
} | ||
} |
112 changes: 90 additions & 22 deletions
112
api/src/main/java/run/halo/app/extension/router/selector/FieldSelector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,117 @@ | ||
package run.halo.app.extension.router.selector; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.function.Predicate; | ||
import java.util.Objects; | ||
import java.util.function.UnaryOperator; | ||
import lombok.Data; | ||
import lombok.experimental.Accessors; | ||
import org.springframework.util.Assert; | ||
|
||
@Data | ||
@Accessors(chain = true) | ||
public class FieldSelector implements Predicate<String> { | ||
private List<SelectorMatcher> matchers; | ||
public class FieldSelector { | ||
private SelectorMatcher matcher; | ||
|
||
@Override | ||
public boolean test(String fieldValue) { | ||
if (matchers == null || matchers.isEmpty()) { | ||
return true; | ||
} | ||
return matchers.stream() | ||
.allMatch(matcher -> matcher.test(fieldValue)); | ||
public static FieldSelectorBuilder builder() { | ||
return new FieldSelectorBuilder(null); | ||
} | ||
|
||
public static FieldSelectorBuilder builder() { | ||
return new FieldSelectorBuilder(); | ||
public static FieldSelectorBuilder builder(SelectorMatcher rootMatcher) { | ||
return new FieldSelectorBuilder(rootMatcher); | ||
} | ||
|
||
public boolean test(UnaryOperator<String> valueForKeyFunc) { | ||
return evaluate(matcher, valueForKeyFunc); | ||
} | ||
|
||
boolean evaluate(SelectorMatcher matcher, UnaryOperator<String> valueForKeyFunc) { | ||
if (matcher instanceof LogicalMatcher) { | ||
if (matcher instanceof AndSelectorMatcher andNode) { | ||
return evaluate(andNode.getLeft(), valueForKeyFunc) | ||
&& evaluate(andNode.getRight(), valueForKeyFunc); | ||
} else if (matcher instanceof OrSelectorMatcher orNode) { | ||
return evaluate(orNode.getLeft(), valueForKeyFunc) | ||
|| evaluate(orNode.getRight(), valueForKeyFunc); | ||
} else if (matcher instanceof AnySelectorMatcher) { | ||
return true; | ||
} | ||
} | ||
String valueToTest = valueForKeyFunc.apply(matcher.getKey()); | ||
return matcher.test(valueToTest); | ||
} | ||
|
||
public static class FieldSelectorBuilder { | ||
private final List<SelectorMatcher> matchers = new ArrayList<>(); | ||
private SelectorMatcher rootMatcher; | ||
|
||
public FieldSelectorBuilder(SelectorMatcher rootMatcher) { | ||
this.rootMatcher = rootMatcher; | ||
} | ||
|
||
public FieldSelectorBuilder eq(String fieldName, String fieldValue) { | ||
return and(EqualityMatcher.equal(fieldName, fieldValue)); | ||
} | ||
|
||
public FieldSelectorBuilder notEq(String fieldName, String fieldValue) { | ||
return and(EqualityMatcher.notEqual(fieldName, fieldValue)); | ||
} | ||
|
||
public FieldSelectorBuilder eq(String fieldPath, String value) { | ||
matchers.add(EqualityMatcher.equal(fieldPath, value)); | ||
public FieldSelectorBuilder in(String fieldName, String... fieldValues) { | ||
return and(SetMatcher.in(fieldName, fieldValues)); | ||
} | ||
|
||
public FieldSelectorBuilder notIn(String fieldName, String... fieldValues) { | ||
return and(SetMatcher.notIn(fieldName, fieldValues)); | ||
} | ||
|
||
public FieldSelectorBuilder exists(String fieldName) { | ||
return and(SetMatcher.exists(fieldName)); | ||
} | ||
|
||
public FieldSelectorBuilder notExists(String fieldName) { | ||
return and(SetMatcher.notExists(fieldName)); | ||
} | ||
|
||
/** | ||
* Combine the current selector matcher with another one with AND. | ||
* | ||
* @param other another selector matcher to be combined with the current one with AND | ||
* @return the current selector matcher builder | ||
*/ | ||
public FieldSelectorBuilder and(SelectorMatcher other) { | ||
Assert.notNull(other, "Other selector matcher must not be null"); | ||
if (rootMatcher == null) { | ||
this.rootMatcher = other; | ||
return this; | ||
} | ||
this.rootMatcher = new AndSelectorMatcher(rootMatcher, other); | ||
return this; | ||
} | ||
|
||
public FieldSelectorBuilder notEq(String fieldPath, String value) { | ||
matchers.add(EqualityMatcher.notEqual(fieldPath, value)); | ||
/** | ||
* Combine the current selector matcher with another one with OR. | ||
* | ||
* @param other another selector matcher to be combined with the current one with OR | ||
* @return the current selector matcher builder | ||
*/ | ||
public FieldSelectorBuilder or(SelectorMatcher other) { | ||
Assert.notNull(other, "Other selector matcher must not be null"); | ||
if (rootMatcher == null) { | ||
rootMatcher = other; | ||
} | ||
rootMatcher = new OrSelectorMatcher(rootMatcher, other); | ||
return this; | ||
} | ||
|
||
/** | ||
* Build a field selector. | ||
* Build the selector matcher. | ||
*/ | ||
public FieldSelector build() { | ||
FieldSelector fieldSelector = new FieldSelector(); | ||
fieldSelector.setMatchers(matchers); | ||
var fieldSelector = new FieldSelector(); | ||
fieldSelector.setMatcher(buildMatcher()); | ||
return fieldSelector; | ||
} | ||
|
||
public SelectorMatcher buildMatcher() { | ||
return Objects.requireNonNullElseGet(rootMatcher, AnySelectorMatcher::new); | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
api/src/main/java/run/halo/app/extension/router/selector/LogicalMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package run.halo.app.extension.router.selector; | ||
|
||
public abstract class LogicalMatcher implements SelectorMatcher { | ||
|
||
@Override | ||
public String getKey() { | ||
throw new UnsupportedOperationException(); | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
api/src/main/java/run/halo/app/extension/router/selector/OrSelectorMatcher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package run.halo.app.extension.router.selector; | ||
|
||
import lombok.Getter; | ||
|
||
@Getter | ||
public class OrSelectorMatcher extends LogicalMatcher { | ||
private final SelectorMatcher left; | ||
private final SelectorMatcher right; | ||
|
||
public OrSelectorMatcher(SelectorMatcher left, SelectorMatcher right) { | ||
this.left = left; | ||
this.right = right; | ||
} | ||
|
||
@Override | ||
public String getKey() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public boolean test(String s) { | ||
return left.test(s) || right.test(s); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
api/src/test/java/run/halo/app/extension/router/selector/FieldSelectorTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package run.halo.app.extension.router.selector; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import java.util.Map; | ||
import org.junit.jupiter.api.Test; | ||
|
||
/** | ||
* Tests for {@link FieldSelector}. | ||
* | ||
* @author guqing | ||
* @since 2.12.0 | ||
*/ | ||
class FieldSelectorTest { | ||
|
||
@Test | ||
void testEq() { | ||
var fieldSelector = FieldSelector.builder() | ||
.eq("name", "guqing").build(); | ||
assertThat(fieldSelector.test(key -> "guqing")).isTrue(); | ||
assertThat(fieldSelector.test(key -> "halo")).isFalse(); | ||
} | ||
|
||
@Test | ||
void testNotEq() { | ||
var fieldSelector = FieldSelector.builder() | ||
.notEq("name", "guqing").build(); | ||
assertThat(fieldSelector.test(key -> "guqing")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "halo")).isTrue(); | ||
} | ||
|
||
@Test | ||
void testIn() { | ||
var fieldSelector = FieldSelector.builder() | ||
.in("name", "guqing", "guqing1").build(); | ||
assertThat(fieldSelector.test(key -> "guqing")).isTrue(); | ||
assertThat(fieldSelector.test(key -> "halo")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "blog")).isFalse(); | ||
} | ||
|
||
@Test | ||
void testNotIn() { | ||
var fieldSelector = FieldSelector.builder() | ||
.notIn("name", "guqing", "guqing1").build(); | ||
assertThat(fieldSelector.test(key -> "guqing")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "halo")).isTrue(); | ||
assertThat(fieldSelector.test(key -> "blog")).isTrue(); | ||
} | ||
|
||
@Test | ||
void testExists() { | ||
var fieldSelector = FieldSelector.builder() | ||
.exists("name").build(); | ||
assertThat(fieldSelector.test(key -> "guqing")).isTrue(); | ||
assertThat(fieldSelector.test(key -> "halo")).isTrue(); | ||
assertThat(fieldSelector.test(key -> "blog")).isTrue(); | ||
} | ||
|
||
@Test | ||
void testNotExists() { | ||
var fieldSelector = FieldSelector.builder() | ||
.notExists("name").build(); | ||
assertThat(fieldSelector.test(key -> "guqing")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "halo")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "blog")).isFalse(); | ||
} | ||
|
||
@Test | ||
void testAnd() { | ||
var fieldSelector = FieldSelector.builder() | ||
.eq("name", "guqing") | ||
.and(FieldSelector.builder().eq("age", "18").build().getMatcher()) | ||
.build(); | ||
assertThat(fieldSelector.test(key -> "guqing")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "halo")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "18")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "guqing18")).isFalse(); | ||
assertThat(fieldSelector.test(key -> { | ||
var map = Map.of("name", "guqing", "age", "18"); | ||
return map.get(key); | ||
})).isTrue(); | ||
assertThat(fieldSelector.test(key -> { | ||
var map = Map.of("name", "guqing", "age", "19"); | ||
return map.get(key); | ||
})).isFalse(); | ||
} | ||
|
||
@Test | ||
void testOr() { | ||
var fieldSelector = FieldSelector.builder() | ||
.eq("name", "guqing") | ||
.or(FieldSelector.builder().eq("age", "18").build().getMatcher()) | ||
.build(); | ||
assertThat(fieldSelector.test(key -> "guqing")).isTrue(); | ||
assertThat(fieldSelector.test(key -> "halo")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "blog")).isFalse(); | ||
assertThat(fieldSelector.test(key -> "18")).isTrue(); | ||
assertThat(fieldSelector.test(key -> "guqing18")).isFalse(); | ||
assertThat(fieldSelector.test(key -> { | ||
var map = Map.of("name", "guqing", "age", "18"); | ||
return map.get(key); | ||
})).isTrue(); | ||
} | ||
} |
Oops, something went wrong.