Skip to content

Commit

Permalink
Correct RxJava2 validation (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
dstepanov authored Mar 2, 2023
1 parent c68e4f6 commit 99cfd3b
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 4 deletions.
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ include 'test-suite-kotlin'
micronautBuild {
importMicronautCatalog()
importMicronautCatalog("micronaut-reactor")
importMicronautCatalog("micronaut-rxjava2")
}

1 change: 1 addition & 0 deletions validation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dependencies {

testImplementation mn.micronaut.http.client
testImplementation mn.micronaut.http.server.netty
testImplementation mnRxjava2.micronaut.rxjava2
testImplementation libs.groovy.json
testImplementation mn.micronaut.inject.java.test
testImplementation mn.micronaut.jackson.databind
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,11 @@ public <T> Publisher<T> validatePublisher(@NonNull ReturnType<?> returnType,
Flux.error(new ConstraintViolationException(violations));
});
}

return Publishers.convertPublisher(conversionService, output, ((ReturnType<Publisher>) returnType).getType());
Class<?> returnClass = returnType.getType();
if (!Publisher.class.isAssignableFrom(returnClass)) {
return (Publisher<T>) output;
}
return Publishers.convertPublisher(conversionService, output, (Class<Publisher>) returnClass);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class BookService {
}

@Executable
void rxValidWithTypeParameter(Mono<List<@Valid Book>> books) {
books.block();
Mono<Void> rxValidWithTypeParameter(Mono<List<@Valid Book>> books) {
return books.then();
}

@Executable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.micronaut.validation.validator.reactive;

import io.micronaut.context.annotation.Executable;
import io.reactivex.Completable;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.List;

@Singleton
class BookServiceRxJava2 {

@Executable
Publisher<@Valid Book> rxSimple(Publisher<@NotBlank String> title) {
return Single.fromPublisher(title).map(Book::new).toFlowable();
}

@Executable
Observable<@Valid Book> rxValid(Observable<@Valid Book> book) {
return book;
}

@Executable
Completable rxValidWithTypeParameter(Single<List<@Valid Book>> books) {
return books.ignoreElement();
}

@Executable
Maybe<@Valid Book> rxValidMaybe(Maybe<@Valid Book> book) { return book; }

@Executable
Publisher<@Valid Book> rxReturnInvalid(Publisher<@Valid Book> book) {
return Flowable.fromPublisher(book).map(b -> new Book(""));
}

@Executable
Maybe<Book> rxReturnInvalidWithoutValidation(Flowable<@Valid Book> books) {
return books.firstElement().map(v -> new Book(""));
}

}


Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ class ReactiveMethodValidationSpec extends Specification {
[Mono.just("")] as Object[]
)

then: "No errors because publisher is not executed"
violations.size() == 0

when:
Mono.from(bookService.rxSimple(Mono.just(""))).block()

then:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package io.micronaut.validation.validator.reactive

import io.micronaut.context.ApplicationContext
import io.micronaut.validation.validator.Validator
import io.reactivex.Flowable
import io.reactivex.Maybe
import io.reactivex.Observable
import io.reactivex.Single
import org.reactivestreams.Publisher
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

import javax.validation.ConstraintViolationException
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.regex.Pattern

class RxJava2MethodValidationSpec extends Specification {

@Shared
@AutoCleanup
ApplicationContext applicationContext = ApplicationContext.run()

void "test reactive return type validation"() {
given:
BookServiceRxJava2 bookService = applicationContext.getBean(BookServiceRxJava2)

when:
Single<Book> single = Single.just(new Book("It"))
Single.fromPublisher(bookService.rxReturnInvalid(single.toFlowable())).blockingGet()

then:
ConstraintViolationException e = thrown()
e.message == 'publisher[]<T Book>.title: must not be blank'
e.getConstraintViolations().first().propertyPath.toString() == 'publisher[]<T Book>.title'
}

void "test reactive return type no validation"() {
given:
BookServiceRxJava2 bookService = applicationContext.getBean(BookServiceRxJava2)

when:
Single<Book> single = Single.just(new Book("It"))
bookService.rxReturnInvalidWithoutValidation(single.toFlowable()).blockingGet()

then:
noExceptionThrown()
}

void "test reactive validation with invalid simple argument"() {
given:
BookServiceRxJava2 bookService = applicationContext.getBean(BookServiceRxJava2)

when:
var validator = applicationContext.getBean(Validator)
var violations = validator.forExecutables().validateParameters(
bookService,
BookService.class.getDeclaredMethod("rxSimple", Publisher<String>),
[Flowable.just("")] as Object[]
)

then: "No errors because publisher is not executed"
violations.size() == 0

when:
Single.fromPublisher(bookService.rxSimple(Single.just("").toFlowable())).blockingGet()

then:
def e = thrown(ConstraintViolationException)
Pattern.matches('rxSimple.title\\[]<T [^>]*String>: must not be blank', e.message)
def path = e.getConstraintViolations().first().propertyPath.iterator()
path.next().getName() == 'rxSimple'
path.next().getName() == 'title'
path.next().isInIterable()

}

void "test reactive validation with valid argument"() {
given:
BookServiceRxJava2 bookService = applicationContext.getBean(BookServiceRxJava2)

when:
def input = Observable.just(new Book("It"))
def book = bookService.rxValid(input).blockingFirst()

then:
book.title == 'It'
}

void "test reactive maybe validation with valid argument"() {
given:
BookServiceRxJava2 bookService = applicationContext.getBean(BookServiceRxJava2)

when:
def input = Maybe.just(new Book("It"))
def book = bookService.rxValidMaybe(input).blockingGet()

then:
book.title == 'It'
}

void "test reactive validation with invalid argument"() {
given:
BookServiceRxJava2 bookService = applicationContext.getBean(BookServiceRxJava2)

when:
def input = Observable.just(new Book(""))
bookService.rxValid(input).blockingFirst()

then:
def e = thrown(ConstraintViolationException)
Pattern.matches('rxValid.book\\[]<T .*Book>.title: must not be blank', e.message)
e.getConstraintViolations().first().propertyPath.toString().startsWith('rxValid.book')
}

void "test reactive validation with invalid argument type parameter"() {
given:
BookServiceRxJava2 bookService = applicationContext.getBean(BookServiceRxJava2)

when:
def input = Single.just([new Book("It"), new Book("")])
bookService.rxValidWithTypeParameter(input).blockingAwait()

then:
def e = thrown(ConstraintViolationException)
Pattern.matches('rxValidWithTypeParameter.books\\[]<T List>\\[1]<E Book>.title: must not be blank', e.message)
e.getConstraintViolations().first().propertyPath.toString().startsWith('rxValidWithTypeParameter.books')
}

}

0 comments on commit 99cfd3b

Please sign in to comment.