mirror of https://github.com/halo-dev/halo
Add label and field selector to Extension list API (#2279)
#### What type of PR is this? /kind feature /kind api-change /area core /milestone 2.0 #### What this PR does / why we need it: Add label and field selector to Extension list API for filtering Extensions. <img width="322" alt="image" src="https://user-images.githubusercontent.com/16865714/181462887-549162fd-5e8d-4cec-834c-24875ada4789.png"> #### Does this PR introduce a user-facing change? ```release-note None ```pull/2290/head
parent
fdbb513cb2
commit
3640dca0a1
|
@ -77,6 +77,7 @@ dependencies {
|
|||
implementation "io.seruco.encoding:base62:$base62"
|
||||
implementation "org.pf4j:pf4j:$pf4j"
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
testCompileOnly 'org.projectlombok:lombok'
|
||||
|
||||
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
|
||||
|
||||
|
@ -87,6 +88,7 @@ dependencies {
|
|||
runtimeOnly 'org.postgresql:postgresql'
|
||||
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testAnnotationProcessor 'org.projectlombok:lombok'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
|
@ -95,5 +97,4 @@ dependencies {
|
|||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
testLogging.showStandardStreams = true
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import run.halo.app.extension.DefaultExtensionClient;
|
|||
import run.halo.app.extension.DefaultSchemeManager;
|
||||
import run.halo.app.extension.DefaultSchemeWatcherManager;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.ExtensionCompositeRouterFunction;
|
||||
import run.halo.app.extension.JSONExtensionConverter;
|
||||
import run.halo.app.extension.SchemeManager;
|
||||
import run.halo.app.extension.SchemeWatcherManager;
|
||||
|
@ -27,6 +26,7 @@ import run.halo.app.extension.SchemeWatcherManager.SchemeWatcher;
|
|||
import run.halo.app.extension.controller.Controller;
|
||||
import run.halo.app.extension.controller.ControllerBuilder;
|
||||
import run.halo.app.extension.controller.ControllerManager;
|
||||
import run.halo.app.extension.router.ExtensionCompositeRouterFunction;
|
||||
import run.halo.app.extension.store.ExtensionStoreClient;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
||||
|
|
|
@ -1,333 +0,0 @@
|
|||
package run.halo.app.extension;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.exception.ExtensionConvertException;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
|
||||
public class ExtensionRouterFunctionFactory {
|
||||
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
public ExtensionRouterFunctionFactory(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public RouterFunction<ServerResponse> create() {
|
||||
var getHandler = new ExtensionGetHandler(scheme, client);
|
||||
var listHandler = new ExtensionListHandler(scheme, client);
|
||||
var createHandler = new ExtensionCreateHandler(scheme, client);
|
||||
var updateHandler = new ExtensionUpdateHandler(scheme, client);
|
||||
var deleteHandler = new ExtensionDeleteHandler(scheme, client);
|
||||
// TODO More handlers here
|
||||
var gvk = scheme.groupVersionKind();
|
||||
var tagName = gvk.toString();
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET(getHandler.pathPattern(), getHandler,
|
||||
builder -> builder.operationId("Get" + gvk)
|
||||
.description("Get " + gvk)
|
||||
.tag(tagName)
|
||||
.parameter(parameterBuilder().in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Name of " + scheme.singular()))
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response single " + scheme.singular())
|
||||
.implementation(scheme.type())))
|
||||
.GET(listHandler.pathPattern(), listHandler,
|
||||
builder -> builder.operationId("List" + gvk)
|
||||
.description("List " + gvk)
|
||||
.tag(tagName)
|
||||
.parameter(parameterBuilder().in(ParameterIn.QUERY)
|
||||
.name("page")
|
||||
.description("Page index")
|
||||
.implementation(Integer.class))
|
||||
.parameter(parameterBuilder().in(ParameterIn.QUERY)
|
||||
.name("size")
|
||||
.description("Size of one page")
|
||||
.implementation(Integer.class))
|
||||
.parameter(parameterBuilder().in(ParameterIn.QUERY)
|
||||
.name("sort")
|
||||
.description("Sort by some fields. Like metadata.name,desc"))
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response " + scheme.plural())
|
||||
.implementation(generateListResultClass())))
|
||||
.POST(createHandler.pathPattern(), createHandler,
|
||||
builder -> builder.operationId("Create" + gvk)
|
||||
.description("Create " + gvk)
|
||||
.tag(tagName)
|
||||
.requestBody(requestBodyBuilder()
|
||||
.description("Fresh " + scheme.singular())
|
||||
.implementation(scheme.type()))
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response " + scheme.plural() + " created just now")
|
||||
.implementation(scheme.type())))
|
||||
.PUT(updateHandler.pathPattern(), updateHandler,
|
||||
builder -> builder.operationId("Update" + gvk)
|
||||
.description("Update " + gvk)
|
||||
.tag(tagName)
|
||||
.parameter(parameterBuilder().in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Name of " + scheme.singular()))
|
||||
.requestBody(requestBodyBuilder()
|
||||
.description("Updated " + scheme.singular())
|
||||
.implementation(scheme.type()))
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response " + scheme.plural() + " updated just now")
|
||||
.implementation(scheme.type())))
|
||||
.DELETE(deleteHandler.pathPattern(), deleteHandler,
|
||||
builder -> builder.operationId("Delete" + gvk)
|
||||
.description("Delete " + gvk)
|
||||
.tag(tagName)
|
||||
.parameter(parameterBuilder().in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Name of " + scheme.singular()))
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response " + scheme.singular() + " deleted just now")))
|
||||
.build();
|
||||
}
|
||||
|
||||
interface PathPatternGenerator {
|
||||
|
||||
String pathPattern();
|
||||
|
||||
static String buildExtensionPathPattern(Scheme scheme) {
|
||||
var gvk = scheme.groupVersionKind();
|
||||
StringBuilder pattern = new StringBuilder();
|
||||
if (gvk.hasGroup()) {
|
||||
pattern.append("/apis/").append(gvk.group());
|
||||
} else {
|
||||
pattern.append("/api");
|
||||
}
|
||||
return pattern.append('/').append(gvk.version()).append('/').append(scheme.plural())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
interface GetHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
interface ListHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
interface CreateHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
interface UpdateHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
interface DeleteHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
static class ExtensionCreateHandler implements CreateHandler {
|
||||
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
public ExtensionCreateHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<ServerResponse> handle(@NonNull ServerRequest request) {
|
||||
return request.bodyToMono(Unstructured.class)
|
||||
.switchIfEmpty(Mono.error(() -> new ExtensionConvertException(
|
||||
"Cannot read body to " + scheme.groupVersionKind())))
|
||||
.flatMap(extToCreate -> Mono.fromCallable(() -> {
|
||||
var name = extToCreate.getMetadata().getName();
|
||||
client.create(extToCreate);
|
||||
return client.fetch(scheme.type(), name)
|
||||
.orElseThrow(() -> new ExtensionNotFoundException(
|
||||
"Extension with name " + name + " was not found"));
|
||||
}))
|
||||
.flatMap(createdExt -> ServerResponse
|
||||
.created(URI.create(pathPattern() + "/" + createdExt.getMetadata().getName()))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(createdExt))
|
||||
.cast(ServerResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return PathPatternGenerator.buildExtensionPathPattern(scheme);
|
||||
}
|
||||
}
|
||||
|
||||
static class ExtensionListHandler implements ListHandler {
|
||||
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
public ExtensionListHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<ServerResponse> handle(@NonNull ServerRequest request) {
|
||||
int page = request.queryParam("page")
|
||||
.filter(StringUtils::hasLength)
|
||||
.map(Integer::parseUnsignedInt).orElse(0);
|
||||
int size = request.queryParam("size")
|
||||
.filter(StringUtils::hasLength)
|
||||
.map(Integer::parseUnsignedInt).orElse(0);
|
||||
// TODO Resolve predicate and comparator from request
|
||||
var listResult = client.list(scheme.type(), null, null, page, size);
|
||||
return ServerResponse
|
||||
.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(listResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return PathPatternGenerator.buildExtensionPathPattern(scheme);
|
||||
}
|
||||
}
|
||||
|
||||
static class ExtensionGetHandler implements GetHandler {
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
public ExtensionGetHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return PathPatternGenerator.buildExtensionPathPattern(scheme) + "/{name}";
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<ServerResponse> handle(@NonNull ServerRequest request) {
|
||||
var extensionName = request.pathVariable("name");
|
||||
|
||||
var extension = client.fetch(scheme.type(), extensionName)
|
||||
.orElseThrow(() -> new ExtensionNotFoundException(
|
||||
scheme.groupVersionKind() + " was not found"));
|
||||
return ServerResponse
|
||||
.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(extension);
|
||||
}
|
||||
}
|
||||
|
||||
static class ExtensionUpdateHandler implements UpdateHandler {
|
||||
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
ExtensionUpdateHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ServerResponse> handle(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return request.bodyToMono(Unstructured.class)
|
||||
.filter(unstructured -> unstructured.getMetadata() != null
|
||||
&& StringUtils.hasText(unstructured.getMetadata().getName())
|
||||
&& Objects.equals(unstructured.getMetadata().getName(), name))
|
||||
.switchIfEmpty(Mono.error(() -> new ExtensionConvertException(
|
||||
"Cannot read body to " + scheme.groupVersionKind())))
|
||||
.flatMap(extToUpdate -> Mono.fromCallable(() -> {
|
||||
client.update(extToUpdate);
|
||||
return client.fetch(scheme.type(), name)
|
||||
.orElseThrow(() -> new ExtensionNotFoundException(
|
||||
"Extension with name " + name + " was not found"));
|
||||
}))
|
||||
.flatMap(updated -> ServerResponse
|
||||
.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(updated));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return PathPatternGenerator.buildExtensionPathPattern(scheme) + "/{name}";
|
||||
}
|
||||
}
|
||||
|
||||
static class ExtensionDeleteHandler implements DeleteHandler {
|
||||
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
ExtensionDeleteHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ServerResponse> handle(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return getExtension(name)
|
||||
.flatMap(extension ->
|
||||
Mono.fromRunnable(() -> client.delete(extension)).thenReturn(extension))
|
||||
.flatMap(extension -> this.getExtension(name))
|
||||
.flatMap(extension -> ServerResponse
|
||||
.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(extension));
|
||||
}
|
||||
|
||||
private Mono<? extends Extension> getExtension(String name) {
|
||||
return Mono.justOrEmpty(client.fetch(scheme.type(), name))
|
||||
.switchIfEmpty(Mono.error(() -> new ExtensionNotFoundException(
|
||||
"Extension with name " + name + " was not found")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return PathPatternGenerator.buildExtensionPathPattern(scheme) + "/{name}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Class<?> generateListResultClass() {
|
||||
var generic =
|
||||
TypeDescription.Generic.Builder.parameterizedType(ListResult.class, scheme.type())
|
||||
.build();
|
||||
return new ByteBuddy()
|
||||
.subclass(generic)
|
||||
.name(scheme.groupVersionKind().kind() + "List")
|
||||
.make()
|
||||
.load(this.getClass().getClassLoader())
|
||||
.getLoaded();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package run.halo.app.extension;
|
||||
package run.halo.app.extension.router;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
@ -11,6 +11,9 @@ import org.springframework.web.reactive.function.server.ServerRequest;
|
|||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.SchemeWatcherManager;
|
||||
import run.halo.app.extension.SchemeWatcherManager.SchemeWatcher;
|
||||
|
||||
public class ExtensionCompositeRouterFunction implements
|
|
@ -0,0 +1,51 @@
|
|||
package run.halo.app.extension.router;
|
||||
|
||||
import java.net.URI;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.Unstructured;
|
||||
import run.halo.app.extension.exception.ExtensionConvertException;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
|
||||
class ExtensionCreateHandler implements ExtensionRouterFunctionFactory.CreateHandler {
|
||||
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
public ExtensionCreateHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<ServerResponse> handle(@NonNull ServerRequest request) {
|
||||
return request.bodyToMono(Unstructured.class)
|
||||
.switchIfEmpty(Mono.error(() -> new ExtensionConvertException(
|
||||
"Cannot read body to " + scheme.groupVersionKind())))
|
||||
.flatMap(extToCreate -> Mono.fromCallable(() -> {
|
||||
var name = extToCreate.getMetadata().getName();
|
||||
client.create(extToCreate);
|
||||
return client.fetch(scheme.type(), name)
|
||||
.orElseThrow(() -> new ExtensionNotFoundException(
|
||||
"Extension with name " + name + " was not found"));
|
||||
}))
|
||||
.flatMap(createdExt -> ServerResponse
|
||||
.created(URI.create(pathPattern() + "/" + createdExt.getMetadata().getName()))
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(createdExt))
|
||||
.cast(ServerResponse.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return ExtensionRouterFunctionFactory.PathPatternGenerator.buildExtensionPathPattern(
|
||||
scheme);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package run.halo.app.extension.router;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.Extension;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
|
||||
class ExtensionDeleteHandler implements ExtensionRouterFunctionFactory.DeleteHandler {
|
||||
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
ExtensionDeleteHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ServerResponse> handle(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return getExtension(name)
|
||||
.flatMap(extension ->
|
||||
Mono.fromRunnable(() -> client.delete(extension)).thenReturn(extension))
|
||||
.flatMap(extension -> this.getExtension(name))
|
||||
.flatMap(extension -> ServerResponse
|
||||
.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(extension));
|
||||
}
|
||||
|
||||
private Mono<? extends Extension> getExtension(String name) {
|
||||
return Mono.justOrEmpty(client.fetch(scheme.type(), name))
|
||||
.switchIfEmpty(Mono.error(() -> new ExtensionNotFoundException(
|
||||
"Extension with name " + name + " was not found")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return ExtensionRouterFunctionFactory.PathPatternGenerator.buildExtensionPathPattern(scheme)
|
||||
+ "/{name}";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package run.halo.app.extension.router;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
|
||||
class ExtensionGetHandler implements ExtensionRouterFunctionFactory.GetHandler {
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
public ExtensionGetHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return ExtensionRouterFunctionFactory.PathPatternGenerator.buildExtensionPathPattern(scheme)
|
||||
+ "/{name}";
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<ServerResponse> handle(@NonNull ServerRequest request) {
|
||||
var extensionName = request.pathVariable("name");
|
||||
|
||||
var extension = client.fetch(scheme.type(), extensionName)
|
||||
.orElseThrow(() -> new ExtensionNotFoundException(
|
||||
scheme.groupVersionKind() + " was not found"));
|
||||
return ServerResponse
|
||||
.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(extension);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package run.halo.app.extension.router;
|
||||
|
||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
||||
|
||||
import org.springframework.boot.convert.ApplicationConversionService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Scheme;
|
||||
|
||||
class ExtensionListHandler implements ExtensionRouterFunctionFactory.ListHandler {
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
public ExtensionListHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<ServerResponse> handle(@NonNull ServerRequest request) {
|
||||
var conversionService = ApplicationConversionService.getSharedInstance();
|
||||
var page =
|
||||
request.queryParam("page")
|
||||
.map(pageString -> conversionService.convert(pageString, Integer.class))
|
||||
.orElse(0);
|
||||
|
||||
var size = request.queryParam("size")
|
||||
.map(sizeString -> conversionService.convert(sizeString, Integer.class))
|
||||
.orElse(0);
|
||||
|
||||
var labelSelectors = request.queryParams().get("labelSelector");
|
||||
var fieldSelectors = request.queryParams().get("fieldSelector");
|
||||
|
||||
// TODO Resolve comparator from request
|
||||
var listResult = client.list(scheme.type(),
|
||||
labelAndFieldSelectorToPredicate(labelSelectors, fieldSelectors), null, page, size);
|
||||
return ServerResponse
|
||||
.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(listResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return ExtensionRouterFunctionFactory.PathPatternGenerator.buildExtensionPathPattern(
|
||||
scheme);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;
|
||||
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.Scheme;
|
||||
|
||||
public class ExtensionRouterFunctionFactory {
|
||||
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
public ExtensionRouterFunctionFactory(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public RouterFunction<ServerResponse> create() {
|
||||
var getHandler = new ExtensionGetHandler(scheme, client);
|
||||
var listHandler = new ExtensionListHandler(scheme, client);
|
||||
var createHandler = new ExtensionCreateHandler(scheme, client);
|
||||
var updateHandler = new ExtensionUpdateHandler(scheme, client);
|
||||
var deleteHandler = new ExtensionDeleteHandler(scheme, client);
|
||||
// TODO More handlers here
|
||||
var gvk = scheme.groupVersionKind();
|
||||
var tagName = gvk.toString();
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET(getHandler.pathPattern(), getHandler,
|
||||
builder -> builder.operationId("Get" + gvk)
|
||||
.description("Get " + gvk)
|
||||
.tag(tagName)
|
||||
.parameter(parameterBuilder().in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Name of " + scheme.singular()))
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response single " + scheme.singular())
|
||||
.implementation(scheme.type())))
|
||||
.GET(listHandler.pathPattern(), listHandler,
|
||||
builder -> {
|
||||
builder.operationId("List" + gvk)
|
||||
.description("List " + gvk)
|
||||
.tag(tagName)
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response " + scheme.plural())
|
||||
.implementation(generateListResultClass()));
|
||||
QueryParamBuildUtil.buildParametersFromType(builder, ListRequest.class);
|
||||
})
|
||||
.POST(createHandler.pathPattern(), createHandler,
|
||||
builder -> builder.operationId("Create" + gvk)
|
||||
.description("Create " + gvk)
|
||||
.tag(tagName)
|
||||
.requestBody(requestBodyBuilder()
|
||||
.description("Fresh " + scheme.singular())
|
||||
.implementation(scheme.type()))
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response " + scheme.plural() + " created just now")
|
||||
.implementation(scheme.type())))
|
||||
.PUT(updateHandler.pathPattern(), updateHandler,
|
||||
builder -> builder.operationId("Update" + gvk)
|
||||
.description("Update " + gvk)
|
||||
.tag(tagName)
|
||||
.parameter(parameterBuilder().in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Name of " + scheme.singular()))
|
||||
.requestBody(requestBodyBuilder()
|
||||
.description("Updated " + scheme.singular())
|
||||
.implementation(scheme.type()))
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response " + scheme.plural() + " updated just now")
|
||||
.implementation(scheme.type())))
|
||||
.DELETE(deleteHandler.pathPattern(), deleteHandler,
|
||||
builder -> builder.operationId("Delete" + gvk)
|
||||
.description("Delete " + gvk)
|
||||
.tag(tagName)
|
||||
.parameter(parameterBuilder().in(ParameterIn.PATH)
|
||||
.name("name")
|
||||
.description("Name of " + scheme.singular()))
|
||||
.response(responseBuilder().responseCode("200")
|
||||
.description("Response " + scheme.singular() + " deleted just now")))
|
||||
.build();
|
||||
}
|
||||
|
||||
interface PathPatternGenerator {
|
||||
|
||||
String pathPattern();
|
||||
|
||||
static String buildExtensionPathPattern(Scheme scheme) {
|
||||
var gvk = scheme.groupVersionKind();
|
||||
StringBuilder pattern = new StringBuilder();
|
||||
if (gvk.hasGroup()) {
|
||||
pattern.append("/apis/").append(gvk.group());
|
||||
} else {
|
||||
pattern.append("/api");
|
||||
}
|
||||
return pattern.append('/').append(gvk.version()).append('/').append(scheme.plural())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
interface GetHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
interface ListHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
interface CreateHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
interface UpdateHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
interface DeleteHandler extends HandlerFunction<ServerResponse>, PathPatternGenerator {
|
||||
|
||||
}
|
||||
|
||||
private Class<?> generateListResultClass() {
|
||||
var generic =
|
||||
TypeDescription.Generic.Builder.parameterizedType(ListResult.class, scheme.type())
|
||||
.build();
|
||||
return new ByteBuddy()
|
||||
.subclass(generic)
|
||||
.name(scheme.groupVersionKind().kind() + "List")
|
||||
.make()
|
||||
.load(this.getClass().getClassLoader())
|
||||
.getLoaded();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package run.halo.app.extension.router;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.Unstructured;
|
||||
import run.halo.app.extension.exception.ExtensionConvertException;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
|
||||
class ExtensionUpdateHandler implements ExtensionRouterFunctionFactory.UpdateHandler {
|
||||
|
||||
private final Scheme scheme;
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
ExtensionUpdateHandler(Scheme scheme, ExtensionClient client) {
|
||||
this.scheme = scheme;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ServerResponse> handle(ServerRequest request) {
|
||||
String name = request.pathVariable("name");
|
||||
return request.bodyToMono(Unstructured.class)
|
||||
.filter(unstructured -> unstructured.getMetadata() != null
|
||||
&& StringUtils.hasText(unstructured.getMetadata().getName())
|
||||
&& Objects.equals(unstructured.getMetadata().getName(), name))
|
||||
.switchIfEmpty(Mono.error(() -> new ExtensionConvertException(
|
||||
"Cannot read body to " + scheme.groupVersionKind())))
|
||||
.flatMap(extToUpdate -> Mono.fromCallable(() -> {
|
||||
client.update(extToUpdate);
|
||||
return client.fetch(scheme.type(), name)
|
||||
.orElseThrow(() -> new ExtensionNotFoundException(
|
||||
"Extension with name " + name + " was not found"));
|
||||
}))
|
||||
.flatMap(updated -> ServerResponse
|
||||
.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(updated));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String pathPattern() {
|
||||
return ExtensionRouterFunctionFactory.PathPatternGenerator.buildExtensionPathPattern(scheme)
|
||||
+ "/{name}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package run.halo.app.extension.router;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class ListRequest {
|
||||
|
||||
@Schema(description = "The page number. Zero indicates no page.")
|
||||
private Integer page;
|
||||
|
||||
@Schema(description = "Size of one page. Zero indicates no limit.")
|
||||
private Integer size;
|
||||
|
||||
@Schema(description = "Label selector for filtering.")
|
||||
private List<String> labelSelector;
|
||||
|
||||
@Schema(description = "Field selector for filtering.")
|
||||
private List<String> fieldSelector;
|
||||
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.springdoc.core.fn.builders.arrayschema.Builder.arraySchemaBuilder;
|
||||
import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder;
|
||||
import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder;
|
||||
|
||||
import io.swagger.v3.core.converter.ModelConverters;
|
||||
import io.swagger.v3.oas.annotations.enums.Explode;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterStyle;
|
||||
import io.swagger.v3.oas.models.media.ArraySchema;
|
||||
import io.swagger.v3.oas.models.media.Schema;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.springdoc.core.fn.builders.operation.Builder;
|
||||
|
||||
public final class QueryParamBuildUtil {
|
||||
|
||||
private QueryParamBuildUtil() {
|
||||
}
|
||||
|
||||
private static <T> T defaultIfNull(T t, T defaultValue) {
|
||||
return t == null ? defaultValue : t;
|
||||
}
|
||||
|
||||
private static <T> List<T> defaultIfNull(List<T> list, List<T> defaultValue) {
|
||||
return list == null ? defaultValue : list;
|
||||
}
|
||||
|
||||
private static String toStringOrNull(Object obj) {
|
||||
return obj == null ? null : obj.toString();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public static void buildParametersFromType(Builder operationBuilder, Type queryParamType) {
|
||||
var resolvedSchema =
|
||||
ModelConverters.getInstance().readAllAsResolvedSchema(queryParamType);
|
||||
var properties = (Map<String, Schema>) resolvedSchema.schema.getProperties();
|
||||
var requiredNames = defaultIfNull(resolvedSchema.schema.getRequired(),
|
||||
Collections.emptyList());
|
||||
properties.forEach((propName, propSchema) -> {
|
||||
final var paramBuilder = parameterBuilder().in(ParameterIn.QUERY);
|
||||
paramBuilder.name(propSchema.getName())
|
||||
.description(propSchema.getDescription())
|
||||
.style(ParameterStyle.FORM)
|
||||
.explode(Explode.TRUE);
|
||||
if (requiredNames.contains(propSchema.getName())) {
|
||||
paramBuilder.required(true);
|
||||
}
|
||||
|
||||
if (propSchema instanceof ArraySchema arraySchema) {
|
||||
paramBuilder.array(arraySchemaBuilder()
|
||||
.uniqueItems(defaultIfNull(arraySchema.getUniqueItems(), false))
|
||||
.minItems(defaultIfNull(arraySchema.getMinItems(), 0))
|
||||
.maxItems(defaultIfNull(arraySchema.getMaxItems(), Integer.MAX_VALUE))
|
||||
.arraySchema(convertSchemaBuilder(arraySchema))
|
||||
.schema(convertSchemaBuilder(arraySchema.getItems()))
|
||||
);
|
||||
} else {
|
||||
paramBuilder.schema(convertSchemaBuilder(propSchema));
|
||||
}
|
||||
operationBuilder.parameter(paramBuilder);
|
||||
});
|
||||
}
|
||||
|
||||
private static org.springdoc.core.fn.builders.schema.Builder convertSchemaBuilder(
|
||||
Schema<?> schema) {
|
||||
var allowableValues = new String[0];
|
||||
if (schema.getEnum() != null) {
|
||||
allowableValues = schema.getEnum().stream()
|
||||
.map(Object::toString)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
return schemaBuilder()
|
||||
.name(schema.getName())
|
||||
.type(schema.getType())
|
||||
.description(schema.getDescription())
|
||||
.format(schema.getFormat())
|
||||
.deprecated(defaultIfNull(schema.getDeprecated(), false))
|
||||
.nullable(defaultIfNull(schema.getNullable(), false))
|
||||
.allowableValues(allowableValues)
|
||||
.defaultValue(toStringOrNull(schema.getDefault()))
|
||||
.example(toStringOrNull(schema.getExample()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import run.halo.app.extension.Extension;
|
||||
|
||||
public class FieldCriteriaPredicateConverter<E extends Extension>
|
||||
implements Converter<SelectorCriteria, Predicate<E>> {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Predicate<E> convert(SelectorCriteria criteria) {
|
||||
// current we only support name field.
|
||||
return ext -> {
|
||||
if ("name".equals(criteria.key())) {
|
||||
var name = ext.getMetadata().getName();
|
||||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
switch (criteria.operator()) {
|
||||
case Equals -> {
|
||||
return criteria.values().contains(name);
|
||||
}
|
||||
case NotEquals -> {
|
||||
return !criteria.values().contains(name);
|
||||
}
|
||||
default -> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.NonNull;
|
||||
import run.halo.app.extension.Extension;
|
||||
|
||||
public class LabelCriteriaPredicateConverter<E extends Extension>
|
||||
implements Converter<SelectorCriteria, Predicate<E>> {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Predicate<E> convert(SelectorCriteria criteria) {
|
||||
return ext -> {
|
||||
var labels = ext.getMetadata().getLabels();
|
||||
switch (criteria.operator()) {
|
||||
case Equals -> {
|
||||
if (labels == null || !labels.containsKey(criteria.key())) {
|
||||
return false;
|
||||
}
|
||||
return criteria.values().contains(labels.get(criteria.key()));
|
||||
}
|
||||
case NotEquals -> {
|
||||
if (labels == null || !labels.containsKey(criteria.key())) {
|
||||
return false;
|
||||
}
|
||||
return !criteria.values().contains(labels.get(criteria.key()));
|
||||
}
|
||||
case NotExist -> {
|
||||
return labels == null || !labels.containsKey(criteria.key());
|
||||
}
|
||||
case Exist -> {
|
||||
return labels != null && labels.containsKey(criteria.key());
|
||||
}
|
||||
default -> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import java.util.Set;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public enum Operator implements Converter<String, SelectorCriteria> {
|
||||
|
||||
Equals("=", 2) {
|
||||
@Override
|
||||
@Nullable
|
||||
public SelectorCriteria convert(@Nullable String selector) {
|
||||
if (preFlightCheck(selector, 3)) {
|
||||
var i = selector.indexOf(getOperator());
|
||||
if (i > 0 && (i + getOperator().length()) < selector.length() - 1) {
|
||||
String key = selector.substring(0, i);
|
||||
String value = selector.substring(i + getOperator().length());
|
||||
return new SelectorCriteria(key, this, Set.of(value));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
NotEquals("!=", 1) {
|
||||
@Override
|
||||
@Nullable
|
||||
public SelectorCriteria convert(@Nullable String selector) {
|
||||
if (preFlightCheck(selector, 4)) {
|
||||
var i = selector.indexOf(getOperator());
|
||||
if (i > 0 && (i + getOperator().length()) < selector.length() - 1) {
|
||||
String key = selector.substring(0, i);
|
||||
String value = selector.substring(i + getOperator().length());
|
||||
return new SelectorCriteria(key, this, Set.of(value));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
NotExist("!", 0) {
|
||||
@Override
|
||||
@Nullable
|
||||
public SelectorCriteria convert(@Nullable String selector) {
|
||||
if (preFlightCheck(selector, 2)) {
|
||||
if (selector.startsWith(getOperator())) {
|
||||
return new SelectorCriteria(selector.substring(1), this, Set.of());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
Exist("", Integer.MAX_VALUE) {
|
||||
@Override
|
||||
public SelectorCriteria convert(String selector) {
|
||||
if (preFlightCheck(selector, 1)) {
|
||||
// TODO validate the source with regex in the future
|
||||
return new SelectorCriteria(selector, this, Set.of());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
private final String operator;
|
||||
|
||||
/**
|
||||
* Parse order.
|
||||
*/
|
||||
private final int order;
|
||||
|
||||
Operator(String operator, int order) {
|
||||
this.operator = operator;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public String getOperator() {
|
||||
return operator;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
protected boolean preFlightCheck(String selector, int minLength) {
|
||||
return selector != null && selector.length() >= minLength;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@Slf4j
|
||||
public class SelectorConverter implements Converter<String, SelectorCriteria> {
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public SelectorCriteria convert(@Nullable String selector) {
|
||||
return Arrays.stream(Operator.values())
|
||||
.sorted(Comparator.comparing(Operator::getOrder))
|
||||
.map(operator -> {
|
||||
log.debug("Resolving selector: {} with operator: {}", selector, operator);
|
||||
return operator.convert(selector);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public record SelectorCriteria(String key, Operator operator, Set<String> values) {
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import org.springframework.data.util.Predicates;
|
||||
import org.springframework.web.server.ServerWebInputException;
|
||||
import run.halo.app.extension.Extension;
|
||||
|
||||
public final class SelectorUtil {
|
||||
|
||||
private SelectorUtil() {
|
||||
}
|
||||
|
||||
public static <E extends Extension> Predicate<E> labelSelectorsToPredicate(
|
||||
List<String> labelSelectors) {
|
||||
if (labelSelectors == null) {
|
||||
labelSelectors = List.of();
|
||||
}
|
||||
|
||||
final var labelPredicateConverter =
|
||||
new SelectorConverter().andThen(new LabelCriteriaPredicateConverter<E>());
|
||||
|
||||
return labelSelectors.stream()
|
||||
.map(selector -> {
|
||||
var predicate = labelPredicateConverter.convert(selector);
|
||||
if (predicate == null) {
|
||||
throw new ServerWebInputException("Invalid label selector: " + selector);
|
||||
}
|
||||
return predicate;
|
||||
})
|
||||
.reduce(Predicate::and)
|
||||
.orElse(Predicates.isTrue());
|
||||
}
|
||||
|
||||
public static <E extends Extension> Predicate<E> fieldSelectorToPredicate(
|
||||
List<String> fieldSelectors) {
|
||||
if (fieldSelectors == null) {
|
||||
fieldSelectors = List.of();
|
||||
}
|
||||
|
||||
final var fieldPredicateConverter =
|
||||
new SelectorConverter().andThen(new FieldCriteriaPredicateConverter<E>());
|
||||
|
||||
return fieldSelectors.stream()
|
||||
.map(selector -> {
|
||||
var predicate = fieldPredicateConverter.convert(selector);
|
||||
if (predicate == null) {
|
||||
throw new ServerWebInputException("Invalid field selector: " + selector);
|
||||
}
|
||||
return predicate;
|
||||
})
|
||||
.reduce(Predicate::and)
|
||||
.orElse(Predicates.isTrue());
|
||||
}
|
||||
|
||||
public static <E extends Extension> Predicate<E> labelAndFieldSelectorToPredicate(
|
||||
List<String> labelSelectors, List<String> fieldSelectors) {
|
||||
return SelectorUtil.<E>labelSelectorsToPredicate(labelSelectors)
|
||||
.and(fieldSelectorToPredicate(fieldSelectors));
|
||||
}
|
||||
}
|
|
@ -114,6 +114,7 @@ class ExtensionConfigurationTest {
|
|||
|
||||
var metadata = new Metadata();
|
||||
metadata.setName("my-fake");
|
||||
metadata.setLabels(Map.of("label-key", "label-value"));
|
||||
var fake = new FakeExtension();
|
||||
fake.setMetadata(metadata);
|
||||
|
||||
|
@ -158,14 +159,37 @@ class ExtensionConfigurationTest {
|
|||
@Test
|
||||
@WithMockUser
|
||||
void shouldListExtensionsWhenSchemeRegistered() {
|
||||
webClient.get()
|
||||
.uri("/apis/fake.halo.run/v1alpha1/fakes")
|
||||
webClient.get().uri("/apis/fake.halo.run/v1alpha1/fakes")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBodyList(FakeExtension.class)
|
||||
.hasSize(1);
|
||||
.expectBody().jsonPath("$.items.length()").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void shouldListExtensionsWithMatchedSelectors() {
|
||||
webClient.get().uri(uriBuilder -> uriBuilder
|
||||
.path("/apis/fake.halo.run/v1alpha1/fakes")
|
||||
.queryParam("labelSelector", "label-key=label-value")
|
||||
.queryParam("fieldSelector", "name=my-fake")
|
||||
.build())
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().jsonPath("$.items.length()").isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void shouldListExtensionsWithMismatchedSelectors() {
|
||||
webClient.get().uri(uriBuilder -> uriBuilder
|
||||
.path("/apis/fake.halo.run/v1alpha1/fakes")
|
||||
.queryParam("labelSelector", "label-key=invalid-label-value")
|
||||
.queryParam("fieldSelector", "name=invalid-name")
|
||||
.build())
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody().jsonPath("$.items.length()").isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
|
@ -219,20 +243,6 @@ class ExtensionConfigurationTest {
|
|||
.getResponseBody();
|
||||
}
|
||||
|
||||
|
||||
WebTestClient.ResponseSpec getCreateExtensionResponse() {
|
||||
var metadata = new Metadata();
|
||||
metadata.setName("my-fake");
|
||||
var fake = new FakeExtension();
|
||||
fake.setMetadata(metadata);
|
||||
|
||||
return webClient.post()
|
||||
.uri("/apis/fake.halo.run/v1alpha1/fakes")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(fake)
|
||||
.exchange();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package run.halo.app.extension;
|
||||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
@ -15,6 +15,10 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
|||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
import org.springframework.web.reactive.function.server.HandlerStrategies;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.SchemeWatcherManager;
|
||||
import run.halo.app.extension.SchemeWatcherManager.SchemeRegistered;
|
||||
import run.halo.app.extension.SchemeWatcherManager.SchemeUnregistered;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package run.halo.app.extension;
|
||||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -24,7 +24,11 @@ import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
|||
import org.springframework.web.reactive.function.server.EntityResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.ExtensionCreateHandler;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.Unstructured;
|
||||
import run.halo.app.extension.exception.ExtensionConvertException;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package run.halo.app.extension;
|
||||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -23,7 +23,11 @@ import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
|||
import org.springframework.web.reactive.function.server.EntityResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.ExtensionDeleteHandler;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.Unstructured;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
|
@ -1,4 +1,4 @@
|
|||
package run.halo.app.extension;
|
||||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -16,7 +16,9 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||
import org.springframework.web.reactive.function.server.EntityResponse;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.ExtensionGetHandler;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
|
@ -1,4 +1,4 @@
|
|||
package run.halo.app.extension;
|
||||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -17,7 +17,10 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
||||
import org.springframework.web.reactive.function.server.EntityResponse;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.ExtensionListHandler;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.ListResult;
|
||||
import run.halo.app.extension.Scheme;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ExtensionListHandlerTest {
|
||||
|
@ -39,7 +42,6 @@ class ExtensionListHandlerTest {
|
|||
var listHandler = new ExtensionListHandler(scheme, client);
|
||||
var serverRequest = MockServerRequest.builder().build();
|
||||
final var fake = new FakeExtension();
|
||||
// when(client.list(same(FakeExtension.class), any(), any())).thenReturn(List.of(fake));
|
||||
var fakeListResult = new ListResult<>(0, 0, 1, List.of(fake));
|
||||
when(client.list(same(FakeExtension.class), any(), any(), anyInt(), anyInt()))
|
||||
.thenReturn(fakeListResult);
|
|
@ -1,4 +1,4 @@
|
|||
package run.halo.app.extension;
|
||||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
|
||||
|
@ -15,10 +15,14 @@ import org.springframework.web.reactive.function.server.HandlerStrategies;
|
|||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.CreateHandler;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.GetHandler;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.ListHandler;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.UpdateHandler;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.router.ExtensionRouterFunctionFactory;
|
||||
import run.halo.app.extension.router.ExtensionRouterFunctionFactory.CreateHandler;
|
||||
import run.halo.app.extension.router.ExtensionRouterFunctionFactory.GetHandler;
|
||||
import run.halo.app.extension.router.ExtensionRouterFunctionFactory.ListHandler;
|
||||
import run.halo.app.extension.router.ExtensionRouterFunctionFactory.UpdateHandler;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ExtensionRouterFunctionFactoryTest {
|
|
@ -1,4 +1,4 @@
|
|||
package run.halo.app.extension;
|
||||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
@ -25,7 +25,11 @@ import org.springframework.mock.web.reactive.function.server.MockServerRequest;
|
|||
import org.springframework.web.reactive.function.server.EntityResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.ExtensionUpdateHandler;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.Unstructured;
|
||||
import run.halo.app.extension.exception.ExtensionConvertException;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
package run.halo.app.extension;
|
||||
package run.halo.app.extension.router;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import run.halo.app.extension.ExtensionRouterFunctionFactory.PathPatternGenerator;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.GVK;
|
||||
import run.halo.app.extension.Scheme;
|
||||
import run.halo.app.extension.router.ExtensionRouterFunctionFactory.PathPatternGenerator;
|
||||
|
||||
class PathPatternGeneratorTest {
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import run.halo.app.extension.Extension;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.Metadata;
|
||||
|
||||
class FieldCriteriaPredicateConverterTest {
|
||||
|
||||
FieldCriteriaPredicateConverter<Extension> converter;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
converter = new FieldCriteriaPredicateConverter<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertNameEqualsCorrectly() {
|
||||
var criteria = new SelectorCriteria("name", Operator.Equals, Set.of("value1", "value2"));
|
||||
var predicate = converter.convert(criteria);
|
||||
assertNotNull(predicate);
|
||||
|
||||
var fakeExt = new FakeExtension();
|
||||
var metadata = new Metadata();
|
||||
fakeExt.setMetadata(metadata);
|
||||
assertFalse(predicate.test(fakeExt));
|
||||
|
||||
metadata.setName("value1");
|
||||
assertTrue(predicate.test(fakeExt));
|
||||
|
||||
metadata.setName("value2");
|
||||
assertTrue(predicate.test(fakeExt));
|
||||
|
||||
metadata.setName("invalid-value");
|
||||
assertFalse(predicate.test(fakeExt));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertNameNotEqualsCorrectly() {
|
||||
var criteria = new SelectorCriteria("name", Operator.NotEquals, Set.of("value1", "value2"));
|
||||
var predicate = converter.convert(criteria);
|
||||
assertNotNull(predicate);
|
||||
|
||||
var fake = new FakeExtension();
|
||||
var metadata = new Metadata();
|
||||
fake.setMetadata(metadata);
|
||||
assertFalse(predicate.test(fake));
|
||||
|
||||
metadata.setName("not-contain-value");
|
||||
assertTrue(predicate.test(fake));
|
||||
|
||||
metadata.setName("value1");
|
||||
assertFalse(predicate.test(fake));
|
||||
|
||||
metadata.setName("value2");
|
||||
assertFalse(predicate.test(fake));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAlwaysFalseIfCriteriaKeyNotSupported() {
|
||||
var criteria =
|
||||
new SelectorCriteria("unsupported-field", Operator.Equals, Set.of("value1", "value2"));
|
||||
var predicate = converter.convert(criteria);
|
||||
assertNotNull(predicate);
|
||||
|
||||
assertFalse(predicate.test(mock(Extension.class)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import run.halo.app.extension.Extension;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.Metadata;
|
||||
|
||||
class LabelCriteriaPredicateConverterTest {
|
||||
|
||||
LabelCriteriaPredicateConverter<Extension> convert;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
convert = new LabelCriteriaPredicateConverter<>();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertEqualsCorrectly() {
|
||||
var criteria = new SelectorCriteria("name", Operator.Equals, Set.of("value1", "value2"));
|
||||
var predicate = convert.convert(criteria);
|
||||
assertNotNull(predicate);
|
||||
var fakeExt = new FakeExtension();
|
||||
var metadata = new Metadata();
|
||||
fakeExt.setMetadata(metadata);
|
||||
assertFalse(predicate.test(fakeExt));
|
||||
|
||||
metadata.setLabels(Map.of("name", "value"));
|
||||
assertFalse(predicate.test(fakeExt));
|
||||
|
||||
metadata.setLabels(Map.of("name", "value1"));
|
||||
assertTrue(predicate.test(fakeExt));
|
||||
|
||||
metadata.setLabels(Map.of("name", "value2"));
|
||||
assertTrue(predicate.test(fakeExt));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertNotEqualsCorrectly() {
|
||||
var criteria = new SelectorCriteria("name", Operator.NotEquals, Set.of("value1", "value2"));
|
||||
var predicate = convert.convert(criteria);
|
||||
assertNotNull(predicate);
|
||||
|
||||
var fakeExt = new FakeExtension();
|
||||
var metadata = new Metadata();
|
||||
fakeExt.setMetadata(metadata);
|
||||
assertFalse(predicate.test(fakeExt));
|
||||
|
||||
metadata.setLabels(Map.of("name", "value"));
|
||||
assertTrue(predicate.test(fakeExt));
|
||||
|
||||
metadata.setLabels(Map.of("name", "value1"));
|
||||
assertFalse(predicate.test(fakeExt));
|
||||
|
||||
metadata.setLabels(Map.of("name", "value2"));
|
||||
assertFalse(predicate.test(fakeExt));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertNotExistCorrectly() {
|
||||
var criteria = new SelectorCriteria("name", Operator.NotExist, Set.of());
|
||||
var predicate = convert.convert(criteria);
|
||||
assertNotNull(predicate);
|
||||
|
||||
var fake = new FakeExtension();
|
||||
var metadata = new Metadata();
|
||||
fake.setMetadata(metadata);
|
||||
assertTrue(predicate.test(fake));
|
||||
|
||||
metadata.setLabels(Map.of("not-a-name", ""));
|
||||
assertTrue(predicate.test(fake));
|
||||
|
||||
metadata.setLabels(Map.of("name", ""));
|
||||
assertFalse(predicate.test(fake));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertExistCorrectly() {
|
||||
var criteria = new SelectorCriteria("name", Operator.Exist, Set.of());
|
||||
var predicate = convert.convert(criteria);
|
||||
assertNotNull(predicate);
|
||||
|
||||
var fake = new FakeExtension();
|
||||
var metadata = new Metadata();
|
||||
fake.setMetadata(metadata);
|
||||
assertFalse(predicate.test(fake));
|
||||
|
||||
metadata.setLabels(Map.of("not-a-name", ""));
|
||||
assertFalse(predicate.test(fake));
|
||||
|
||||
metadata.setLabels(Map.of("name", ""));
|
||||
assertTrue(predicate.test(fake));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static run.halo.app.extension.router.selector.Operator.Equals;
|
||||
import static run.halo.app.extension.router.selector.Operator.Exist;
|
||||
import static run.halo.app.extension.router.selector.Operator.NotEquals;
|
||||
import static run.halo.app.extension.router.selector.Operator.NotExist;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@Slf4j
|
||||
class OperatorTest {
|
||||
|
||||
@Test
|
||||
void shouldConvertCorrectly() {
|
||||
record TestCase(String source, Operator converter, SelectorCriteria expected) {
|
||||
}
|
||||
|
||||
List.of(
|
||||
new TestCase("", Equals, null),
|
||||
new TestCase("=", Equals, null),
|
||||
new TestCase("=value", Equals, null),
|
||||
new TestCase("name=", Equals, null),
|
||||
new TestCase("name=value", Equals,
|
||||
new SelectorCriteria("name", Equals, Set.of("value"))),
|
||||
|
||||
new TestCase("", NotEquals, null),
|
||||
new TestCase("=", NotEquals, null),
|
||||
new TestCase("!", NotEquals, null),
|
||||
new TestCase("!=", NotEquals, null),
|
||||
new TestCase("!=value", NotEquals, null),
|
||||
new TestCase("name!=", NotEquals, null),
|
||||
new TestCase("name!=value", NotEquals,
|
||||
new SelectorCriteria("name", NotEquals, Set.of("value"))),
|
||||
|
||||
new TestCase("", NotExist, null),
|
||||
new TestCase("!", NotExist, null),
|
||||
new TestCase("!name", NotExist, new SelectorCriteria("name", NotExist, Set.of())),
|
||||
new TestCase("name", NotExist, null),
|
||||
new TestCase("na!me", NotExist, null),
|
||||
new TestCase("name!", NotExist, null),
|
||||
|
||||
new TestCase("name", Exist, new SelectorCriteria("name", Exist, Set.of())),
|
||||
new TestCase("", Exist, null),
|
||||
new TestCase("!", Exist, new SelectorCriteria("!", Exist, Set.of())),
|
||||
new TestCase("a", Exist, new SelectorCriteria("a", Exist, Set.of()))
|
||||
).forEach(testCase -> {
|
||||
log.debug("Testing: {}", testCase);
|
||||
assertEquals(testCase.expected(), testCase.converter().convert(testCase.source()));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static run.halo.app.extension.router.selector.Operator.Equals;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@Slf4j
|
||||
class SelectorConverterTest {
|
||||
|
||||
SelectorConverter converter = new SelectorConverter();
|
||||
|
||||
@Test
|
||||
void shouldConvertCorrectly() {
|
||||
record TestCase(String selector, SelectorCriteria expected) {
|
||||
}
|
||||
|
||||
List.of(
|
||||
new TestCase("", null),
|
||||
new TestCase("name=value",
|
||||
new SelectorCriteria("name", Equals, Set.of("value"))),
|
||||
new TestCase("name!=value",
|
||||
new SelectorCriteria("name", Operator.NotEquals, Set.of("value"))),
|
||||
new TestCase("name",
|
||||
new SelectorCriteria("name", Operator.Exist, Set.of())),
|
||||
new TestCase("!name",
|
||||
new SelectorCriteria("name", Operator.NotExist, Set.of())),
|
||||
new TestCase("name",
|
||||
new SelectorCriteria("name", Operator.Exist, Set.of())),
|
||||
new TestCase("name!=",
|
||||
new SelectorCriteria("name!=", Operator.Exist, Set.of())),
|
||||
new TestCase("==",
|
||||
new SelectorCriteria("==", Operator.Exist, Set.of()))
|
||||
).forEach(testCase -> {
|
||||
log.debug("Testing: {}", testCase);
|
||||
assertEquals(testCase.expected, converter.convert(testCase.selector));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package run.halo.app.extension.router.selector;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import run.halo.app.extension.Extension;
|
||||
import run.halo.app.extension.FakeExtension;
|
||||
import run.halo.app.extension.Metadata;
|
||||
|
||||
class SelectorUtilTest {
|
||||
|
||||
@Test
|
||||
void shouldConvertCorrectlyIfSelectorsAreNull() {
|
||||
var predicate = labelAndFieldSelectorToPredicate(null, null);
|
||||
assertTrue(predicate.test(mock(Extension.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertCorrectlyIfSelectorsAreNotNull() {
|
||||
var predicate = labelAndFieldSelectorToPredicate(List.of("label-name=label-value"),
|
||||
List.of("name=fake-name"));
|
||||
assertNotNull(predicate);
|
||||
|
||||
var fake = new FakeExtension();
|
||||
var metadata = new Metadata();
|
||||
fake.setMetadata(metadata);
|
||||
assertFalse(predicate.test(fake));
|
||||
|
||||
metadata.setName("fake-name");
|
||||
assertFalse(predicate.test(fake));
|
||||
|
||||
metadata.setLabels(Map.of("label-name", "label-value"));
|
||||
assertTrue(predicate.test(fake));
|
||||
|
||||
metadata.setName("invalid-name");
|
||||
assertFalse(predicate.test(fake));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue