refactor: add builder for list options (#6148)

#### What type of PR is this?
/kind improvement
/area core
/milestone 2.17.x

#### What this PR does / why we need it:
为 ListOptions 增加 Builder 以方便构建 ListOptions 对象,可以通过以下形式创建
```java
var listOptions = ListOptions.builder()
                .labelSelector()// 构建 LabelSelector
                .eq("key-1", "value-1")
                .notEq("key-2", "value-1")
                .exists("key-3")
                .end()// 结束构建 LabelSelector
                .fieldQuery(equal("spec.title", "fake-title"))  // 构建 FieldSelector
                .andQuery(equal("spec.slug", "fake-slug"))
                .orQuery(equal("spec.slug", "test"))
                .build();
```

#### Does this PR introduce a user-facing change?
```release-note
开发者相关:支持通过 Builder 来简化 ListOptions 的构建
```
pull/6131/head
guqing 2024-06-26 14:44:49 +08:00 committed by GitHub
parent ba2987b585
commit 50f751dda2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 227 additions and 17 deletions

View File

@ -1,9 +1,13 @@
package run.halo.app.extension;
import java.util.List;
import lombok.Data;
import lombok.experimental.Accessors;
import run.halo.app.extension.index.query.Query;
import run.halo.app.extension.index.query.QueryFactory;
import run.halo.app.extension.router.selector.FieldSelector;
import run.halo.app.extension.router.selector.LabelSelector;
import run.halo.app.extension.router.selector.SelectorMatcher;
@Data
@Accessors(chain = true)
@ -15,14 +19,109 @@ public class ListOptions {
public String toString() {
var sb = new StringBuilder();
if (fieldSelector != null) {
sb.append("fieldSelector: ").append(fieldSelector.query());
var query = fieldSelector.query().toString();
sb.append("fieldSelector: ")
.append(query.startsWith("(") ? query : "(" + query + ")");
}
if (labelSelector != null) {
if (!sb.isEmpty()) {
sb.append(", ");
}
sb.append("labelSelector: ").append(labelSelector);
sb.append("labelSelector: (").append(labelSelector).append(")");
}
return sb.toString();
}
public static ListOptionsBuilder builder() {
return new ListOptionsBuilder();
}
public static ListOptionsBuilder builder(ListOptions listOptions) {
return new ListOptionsBuilder(listOptions);
}
public static class ListOptionsBuilder {
private LabelSelectorBuilder labelSelectorBuilder;
private Query query;
public ListOptionsBuilder() {
}
/**
* Create a new list options builder with the given list options.
*/
public ListOptionsBuilder(ListOptions listOptions) {
if (listOptions.getLabelSelector() != null) {
this.labelSelectorBuilder = new LabelSelectorBuilder(
listOptions.getLabelSelector().getMatchers(), this);
}
if (listOptions.getFieldSelector() != null) {
this.query = listOptions.getFieldSelector().query();
}
}
/**
* Create a new label selector builder.
*/
public LabelSelectorBuilder labelSelector() {
if (labelSelectorBuilder == null) {
labelSelectorBuilder = new LabelSelectorBuilder(this);
}
return labelSelectorBuilder;
}
public ListOptionsBuilder fieldQuery(Query query) {
this.query = query;
return this;
}
/**
* And the given query to the current query.
*/
public ListOptionsBuilder andQuery(Query query) {
this.query = (this.query == null ? query : QueryFactory.and(this.query, query));
return this;
}
/**
* Or the given query to the current query.
*/
public ListOptionsBuilder orQuery(Query query) {
this.query = (this.query == null ? query : QueryFactory.or(this.query, query));
return this;
}
/**
* Build the list options.
*/
public ListOptions build() {
var listOptions = new ListOptions();
if (labelSelectorBuilder != null) {
listOptions.setLabelSelector(labelSelectorBuilder.build());
}
if (query != null) {
listOptions.setFieldSelector(FieldSelector.of(query));
}
return listOptions;
}
}
public static class LabelSelectorBuilder
extends LabelSelector.LabelSelectorBuilder<LabelSelectorBuilder> {
private final ListOptionsBuilder listOptionsBuilder;
public LabelSelectorBuilder(List<SelectorMatcher> givenMatchers,
ListOptionsBuilder listOptionsBuilder) {
super(givenMatchers);
this.listOptionsBuilder = listOptionsBuilder;
}
public LabelSelectorBuilder(ListOptionsBuilder listOptionsBuilder) {
this.listOptionsBuilder = listOptionsBuilder;
}
public ListOptionsBuilder end() {
return this.listOptionsBuilder;
}
}
}

View File

@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.lang.NonNull;
@ -24,6 +25,16 @@ public class LabelSelector implements Predicate<Map<String, String>> {
.allMatch(matcher -> matcher.test(labels.get(matcher.getKey())));
}
@Override
public String toString() {
if (matchers == null || matchers.isEmpty()) {
return "";
}
return matchers.stream()
.map(SelectorMatcher::toString)
.collect(Collectors.joining(", "));
}
/**
* Returns a new label selector that is the result of ANDing the current selector with the
* given selector.
@ -40,41 +51,58 @@ public class LabelSelector implements Predicate<Map<String, String>> {
return labelSelector;
}
public static LabelSelectorBuilder builder() {
return new LabelSelectorBuilder();
public static LabelSelectorBuilder<?> builder() {
return new LabelSelectorBuilder<>();
}
public static class LabelSelectorBuilder {
public static class LabelSelectorBuilder<T extends LabelSelectorBuilder<T>> {
private final List<SelectorMatcher> matchers = new ArrayList<>();
public LabelSelectorBuilder eq(String key, String value) {
public LabelSelectorBuilder() {
}
/**
* Create a new label selector builder with the given matchers.
*/
public LabelSelectorBuilder(List<SelectorMatcher> givenMatchers) {
if (givenMatchers != null) {
matchers.addAll(givenMatchers);
}
}
@SuppressWarnings("unchecked")
private T self() {
return (T) this;
}
public T eq(String key, String value) {
matchers.add(EqualityMatcher.equal(key, value));
return this;
return self();
}
public LabelSelectorBuilder notEq(String key, String value) {
public T notEq(String key, String value) {
matchers.add(EqualityMatcher.notEqual(key, value));
return this;
return self();
}
public LabelSelectorBuilder in(String key, String... values) {
public T in(String key, String... values) {
matchers.add(SetMatcher.in(key, values));
return this;
return self();
}
public LabelSelectorBuilder notIn(String key, String... values) {
public T notIn(String key, String... values) {
matchers.add(SetMatcher.notIn(key, values));
return this;
return self();
}
public LabelSelectorBuilder exists(String key) {
public T exists(String key) {
matchers.add(SetMatcher.exists(key));
return this;
return self();
}
public LabelSelectorBuilder notExists(String key) {
public T notExists(String key) {
matchers.add(SetMatcher.notExists(key));
return this;
return self();
}
/**

View File

@ -46,6 +46,14 @@ public class SetMatcher implements SelectorMatcher {
return operator.with(values).test(s);
}
@Override
public String toString() {
if (Operator.EXISTS.equals(operator) || Operator.NOT_EXISTS.equals(operator)) {
return key + " " + operator;
}
return key + " " + operator + " (" + String.join(", ", values) + ")";
}
private enum Operator {
IN(values -> v -> contains(values, v)),
NOT_IN(values -> v -> !contains(values, v)),

View File

@ -0,0 +1,51 @@
package run.halo.app.extension;
import static org.assertj.core.api.Assertions.assertThat;
import static run.halo.app.extension.index.query.QueryFactory.equal;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
/**
* Test for {@link ListOptions}.
*
* @author guqing
* @since 2.17.0
*/
class ListOptionsTest {
@Nested
class ListOptionsBuilderTest {
@Test
void buildTest() {
var listOptions = ListOptions.builder()
.labelSelector()
.eq("key-1", "value-1")
.notEq("key-2", "value-1")
.exists("key-3")
.end()
.andQuery(equal("spec.slug", "fake-slug"))
.orQuery(equal("spec.slug", "test"))
.build();
System.out.println(listOptions);
assertThat(listOptions.toString()).isEqualTo(
"fieldSelector: (spec.slug = 'fake-slug' OR spec.slug = 'test'), labelSelector: "
+ "(key-1 equal value-1, key-2 not_equal value-1, key-3 EXISTS)");
}
@Test
void buildTest2() {
var listOptions = ListOptions.builder()
.labelSelector()
.notEq("key-2", "value-1")
.end()
.fieldQuery(equal("spec.slug", "fake-slug"))
.build();
assertThat(listOptions.toString())
.isEqualTo(
"fieldSelector: (spec.slug = 'fake-slug'), labelSelector: (key-2 not_equal "
+ "value-1)");
}
}
}

View File

@ -0,0 +1,24 @@
package run.halo.app.extension.router.selector;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
/**
* Tests for {@link LabelSelector}.
*
* @author guqing
* @since 2.17.0
*/
class LabelSelectorTest {
@Test
void builderTest() {
var labelSelector = LabelSelector.builder()
.eq("a", "v1")
.in("b", "v2", "v3")
.build();
assertThat(labelSelector.toString())
.isEqualTo("a equal v1, b IN (v2, v3)");
}
}