Adds BooleanOpSpec.not()

This allows to negate specific predicates rather than just the previous predicate.

Fixes gh-1932
This commit is contained in:
spencergibb
2020-09-15 12:01:36 -04:00
parent f961b3625e
commit fa0ffd0a2b
5 changed files with 92 additions and 1 deletions

View File

@@ -39,6 +39,10 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
return new NegateAsyncPredicate<>(this);
}
default AsyncPredicate<T> not(AsyncPredicate<? super T> other) {
return new NegateAsyncPredicate<>(other);
}
default AsyncPredicate<T> or(AsyncPredicate<? super T> other) {
return new OrAsyncPredicate<>(this, other);
}
@@ -84,7 +88,7 @@ public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
@Override
public String toString() {
return String.format("!%s", this.predicate);
return String.format("!(%s)", this.predicate);
}
}

View File

@@ -113,6 +113,27 @@ public class BooleanSpec extends UriSpec {
return new BooleanSpec(this.routeBuilder, this.builder);
}
public BooleanSpec not(Function<PredicateSpec, BooleanSpec> fn) {
return fn
.apply(new NotOpSpec(this.routeBuilder, this.builder, this.operator));
}
}
public static class NotOpSpec extends BooleanOpSpec {
NotOpSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder,
Operator operator) {
super(routeBuilder, builder, operator);
}
@Override
public BooleanSpec asyncPredicate(AsyncPredicate<ServerWebExchange> predicate) {
AsyncPredicate<ServerWebExchange> negated = this.routeBuilder.getPredicate()
.not(predicate);
return super.asyncPredicate(negated);
}
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.route.builder;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.function.Function;
import java.util.function.Predicate;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
@@ -309,4 +310,8 @@ public class PredicateSpec extends UriSpec {
return predicate(exchange -> true);
}
public BooleanSpec not(Function<PredicateSpec, BooleanSpec> fn) {
return alwaysTrue().and().not(fn);
}
}

View File

@@ -74,6 +74,20 @@ public class AsyncPredicateTest {
right.assertTested();
}
@Test
public void negateOperatorWorks() {
TestAsyncPredicate<Object> falsePredicate = new TestAsyncPredicate<>(o -> false);
TestAsyncPredicate<Object> truePredicate = new TestAsyncPredicate<>(o -> true);
Publisher<Boolean> falseNot = falsePredicate.negate().apply(new Object());
Publisher<Boolean> trueNot = truePredicate.negate().apply(new Object());
StepVerifier.create(falseNot).expectNext(true).expectComplete().verify();
StepVerifier.create(trueNot).expectNext(false).expectComplete().verify();
falsePredicate.assertTested();
truePredicate.assertTested();
}
/**
* An AsyncPredicate decorator that records if the apply method was called.
*/

View File

@@ -21,14 +21,21 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.test.BaseWebClientTests;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@@ -57,11 +64,51 @@ public class RoutePredicateHandlerMappingIntegrationTests extends BaseWebClientT
.uri("/get").exchange().expectStatus().isNotFound();
}
@Test
public void andNotWorksWithMissingParameter() {
testClient.get().uri("/andnotquery").exchange().expectBody(String.class)
.isEqualTo("notsupplied");
}
@Test
public void andNotWorksWithParameter() {
testClient.get().uri("/andnotquery?myquery=shouldnotsee").exchange()
.expectBody(String.class).isEqualTo("hasquery");
}
@EnableAutoConfiguration
@SpringBootConfiguration
@Import(DefaultTestConfig.class)
@RestController
public static class TestConfig {
@Value("${test.uri:http://httpbin.org:80}")
String uri;
@GetMapping("/httpbin/andnotquery")
String andnotquery(@RequestParam(name = "myquery",
defaultValue = "notsupplied") String myquery) {
return myquery;
}
@GetMapping("/httpbin/hasquery")
String hasquery() {
return "hasquery";
}
@Bean
RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("and_not_missing_myquery",
r -> r.path("/andnotquery").and().not(p -> p.query("myquery"))
.filters(f -> f.prefixPath("/httpbin")).uri(uri))
.route("and_not_has_myquery",
r -> r.path("/andnotquery").and().query("myquery")
.filters(f -> f.setPath("/httpbin/hasquery"))
.uri(uri))
.build();
}
}
}