Skip to content

Commit

Permalink
refactor: add builder for list options (#6148)
Browse files Browse the repository at this point in the history
#### 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 的构建
```
  • Loading branch information
guqing authored Jun 26, 2024
1 parent ba2987b commit 50f751d
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 17 deletions.
103 changes: 101 additions & 2 deletions api/src/main/java/run/halo/app/extension/ListOptions.java
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
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;
Expand All @@ -24,6 +25,16 @@ public boolean test(@NonNull Map<String, String> labels) {
.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.
Expand All @@ -40,41 +51,58 @@ public LabelSelector and(LabelSelector other) {
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();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ public boolean test(String s) {
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)),
Expand Down
51 changes: 51 additions & 0 deletions api/src/test/java/run/halo/app/extension/ListOptionsTest.java
Original file line number Diff line number Diff line change
@@ -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)");
}
}
}
Original file line number Diff line number Diff line change
@@ -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)");
}
}

0 comments on commit 50f751d

Please sign in to comment.