How does one use a Mono.error(<Throwable>) but attach information from the body returned from a request?
Is there a reactive object that extends Throwable that takes a Mono/Flux object, so the error being thrown will wait for the body to be accounted for?
Or is there a way to add some sort of 'flag' onto an existing Mono object to make it fail instantly (to circumvent the requirement to be Throwable)
Example scenario below:
import org.springframework.web.reactive.function.client.WebClient;
private someMethod() {
webClient.get().retrieve().onStatus(HttpStatus::isError, this::errorHandler)
}
private Mono<? extends Throwable> errorHandler(ClientResponse cr) {
Mono<String> body = cr.body(BodyExtractors.toMono(String.class));
...<do something here>...
return Mono.error(new WhateverException());
}
Thanks
return body.flatMap(str -> Mono.error(new WhateverException(str)));
Related
I want to retrieve single object from Room database, so i have this method in Dao
// in Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity>
That object then will be mapped into others model, let say PlainFoo.
// in Repository
fun getRandomFoo(): Flow<PlainFoo> = dao.getSingleFoo()
.map(FooEntity::asExternalModel)
But in the first launch of this app, the table is empty. It makes the dao function return null and trigger NPE when being mapped. I try to wrap it inside a sealed interface like this.
// Result.kt as wrapper
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Throwable? = null) : Result<Nothing>
}
fun <T> Flow<T>.asResult(): Flow<Result<T>> = this
.map<T, Result<T>> {
Result.Success(it)
}
.catch {
emit(Result.Error(it))
}
And then i call this method in the presentation layer like this.
// in ViewModel
val randomFoo = fooRepository.getRandomFoo().asResult()
// in activity, log only for checking
lifecycleScope.launch {
viewModel.randomFoo.collect {
Timber.tag("RandomFooFlow").d("$it")
}
}
It catches the error, which look like this.
Error(exception=java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter <this>)
But when new data is inserted, it does not get updated unless i reopen the app (which means new Flow is being collected, not the old one). So it seems that the flow is cancelled.
Is there any way to handle this without making my Dao return a
nullable object?
Note: if the data is already populated when opening the app, the flow is able to keep consuming new value).
Instead of dealing with exceptions, I would suggest to return nullable types from your Dao. You can then also update your mapper function to handle the type nullability. You won't need to wrap it into any Result class, just a simple null check on the UI end would suffice.
// Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity?>
// Repo
fun getRandomFoo(): Flow<PlainFoo?> = dao.getSingleFoo().map { it?.asExternalModel() }
Could you please call repository getRandomFoo() method from inside coroutine in view model ? And also you need to call response with data observe like LiveData or StateFlow. By the way, you can wrap your result with wrap inside repository. In code example, I do not care about it because your error is not related with mapping.
View Model
private val _stateFlow = MutableStateFlow()
val stateFlow:StateFlow
fun getRandom(){
fooRepository.getRandomFoo().onEach{
if(it is Result.Success){
stateFlow.value = it
}
}.launchIn(viewModelScope)
}
Fragment or activity
viewLifecycleOwner.lifecycle.repeatOnLifecycle{
stateFlow.collect{
// Listen data for your UI
}
}
The class has a function:
fun theFunc(uri: Uri, theMap: Map<String, String>?, callback: ICallback) {
......
}
and would like to verify it is called with proper params type
io.mockk.verify { mock.theFunc(ofType(Uri::class), ofType(Map<String, String>::class), ofType(ICallbak::class)) }
the ofType(Uri::class) is ok,
the ofType(Map<String, String>::class got error:
the ofType(ICallbak::class) got error:
ICallback does not have a companion object, thus must be initialized
here.
How to use the ofType() for Map and interface?
The problem is that generic parameters are lost at runtime due to type erasure, and for this reason the syntax doesn't allow generic parameters to be specified in that context. You can write Map::class but not Map<String, String>::class because a Map<String, String> is just a Map at runtime.
So, you can call it like this:
verify { mock.theFunc(ofType(Uri::class), ofType(Map::class), ofType(ICallback::class)) }
that will work. However, there is also a version of function ofType which takes generic parameters, so you can use this:
verify { mock.theFunc(ofType<Uri>(), ofType<Map<String, String>>(), ofType<ICallback>()) }
You need to use mapOf<String,String>::class
io.mockk.verify { mock.theFunc(ofType(Uri::class), ofType(mapOf<String,String>()::class), ofType(ICallbak)) }
For interface, you can create mocck object. And put it into ofType.
val callbackMock: ICallback = mockk()
io.mockk.verify { mock.theFunc(ofType(Uri::class), ofType(mapOf<String,String>()::class), ofType(callbackMock::class)) }
I defined a data provider help class to populate my local DB during testing and I'm using a ReactorCrudRepository. Te defined saveAll method is:
<S extends T> Flux<S> saveAll(Publisher<S> entityStream);
And my PostgresDataProvider.insertData is returning a Mono because I'm not interested in receive the inserted entities. How can pipeline my current method from Flux to cast into Mono?
public Mono<Void> insertData(Flux<Entity> entities) {
return repository.saveAll(entities);
}
I finally used the operator
then() → will just replay the source terminal signal, resulting in a Mono to indicate that this never signals any onNext.
That allow me to produce a Mono on the onComplete flux's signal.
public Mono<Void> insertData(Flux<RateEntity> rateEntities) {
return repository.saveAll(rateEntities).then();
}
I have a function that returns either an error message (String) or a Firestore DocumentReference. I was planning to use a class containing both and testing if the error message is non-null to detect an error and if not then the reference is valid. I thought that was far too verbose however, and then thought it may be neater to return a var. Returning a var is not allowed however. Therefore I return a dynamic and test if result is String to detect an error.
IE.
dynamic varResult = insertDoc(_sCollection,
dataRec.toJson());
if (varResult is String) {
Then after checking for compliance, I read the following from one of the gurus:
"It is bad style to explicitly mark a function as returning Dynamic (or var, or Any or whatever you choose to call it). It is very rare that you need to be aware of it (only when instantiating a generic with multiple type arguments where some are known and some are not)."
I'm quite happy using dynamic for the return value if that is appropriate, but generally I try to comply with best practice. I am also very aware of bloated software and I go to extremes to avoid it. That is why I didn't want to use a Class for the return value.
What is the best way to handle the above situation where the return type could be a String or alternatively some other object, in this case a Firestore DocumentReference (emphasis on very compact code)?
One option would be to create an abstract state class. Something like this:
abstract class DocumentInsertionState {
const DocumentInsertionState();
}
class DocumentInsertionError extends DocumentInsertionState {
final String message;
const DocumentInsertionError(this.message);
}
class DocumentInsertionSuccess<T> extends DocumentInsertionState {
final T object;
const DocumentInsertionSuccess(this.object);
}
class Test {
void doSomething() {
final state = insertDoc();
if (state is DocumentInsertionError) {
}
}
DocumentInsertionState insertDoc() {
try {
return DocumentInsertionSuccess("It worked");
} catch (e) {
return DocumentInsertionError(e.toString());
}
}
}
Full example here: https://github.com/ReactiveX/rxdart/tree/master/example/flutter/github_search
I have a situation as follows: I have a LoggingAspect with several pointcuts matching specific method executions in my main application. The corresponding advice bodies basically all look similar, causing a lot of code duplication:
void around() : download() {
String message = "Downloading, verifying (MD5) and unpacking";
SimpleLogger.verbose(message, IndentMode.INDENT_AFTER);
proceed();
SimpleLogger.verbose(message + " - done", IndentMode.DEDENT_BEFORE);
}
There is some variation, though. Sometimes the pointcut & advice have an arg or this parameter which is also printed to the log. Sometimes the "done" message is not printed if it s just a minor call not wrapping a lot of other calls, like this:
void around(BasicFilter filter) : fixFaultyLinkTargets() && this(filter) {
String message = "TOC file: checking for faulty link targets";
SimpleLogger.verbose(message, IndentMode.INDENT_AFTER);
proceed(filter);
SimpleLogger.dedent();
}
The constant thing is that I manually tell the logger
to increase the indent level after the first message is printed, i.e. directly before proceed() is called, and
to decrease the indent level before the final message is printed (if any is printed), i.e. directly after proceed() has returned.
My idea is that I would like to write a meta aspect (or call it a helper aspect) with a pointcut which intercepts the proceed() calls in LoggingAspect so as to automatically adjust the indentation level accordingly. But there seems to be no pointcut matching proceed(). I have tried call(SomeMethodInMyMainApp), even a pointcut matching everything in the logging aspect, but the pointcut matches anything I do not need, but never ever the proceed.
If anybody knows how I can do this, I would appreciate a hint or a code snippet.
An indirect way of doing this might be to intercept not the advice themselves, but the method calls (or executions) advised by those advice by creating an extra pointcut like this:
// ATTENTION: each new pointcut must also be added here
pointcut catchAll() : download() || fixFaultyLinkTargets() || ...;
void around() : catchAll() {
SimpleLogger.indent();
proceed();
SimpleLogger.dedent();
}
I would prefer another way though, without me having to remember to update the extra catchAll() pointcut everytime I change something in the logging aspect.
Suggestion wrap the proceed() in an anonymous class. And the write an aspect which adress this execution (but don't forget potential exceptions of proceed()).
My suggestion:
// AspectProceedCaller.java
public abstract class AspectProceedCaller {
public abstract Object doProceed();
};
// aspect ProceedCallerAspect.aj
aspect ProceedCallerAspect {
pointcut execProceedCaller() : execution( * AspectProceedCaller+.doProceed() );
Object around() : execProceedCaller() {
try {
SimpleLogger.indent();
return proceed();
}
finally {
SimpleLogger.dedent();
}
}
};
// Your application aspect
aspect AnyAspect {
pointcut anyPointcut() : ...;
Object around() : anyPointcut() {
AspectProceedCaller apc=new AspectProceedCaller() {
public Object doProceed() {
return proceed();
}
};
// DO Stuff before ....
Object retval = apc.doProceed();
// ... and after calling proceed.
return retval;
}
};
Best regards Marko
Please note: I am going to answer my own question here, adding more information and the additional feature of parametrisation to the solution suggested by loddar2012. Because his answer led me to the right direction, I am going to accept it even though this answer here really addresses all my needs from the original question, such as (quoting myself):
There is some variation, though. Sometimes the pointcut & advice have an arg or this parameter which is also printed to the log. Sometimes the "done" message is not printed if it s just a minor call not wrapping a lot of other calls
The basic thing we are dealing with here is what Ramnivas Laddad calls the worker object pattern in his book AspectJ in Action. His (and loddar2012's) idea is, in plain prose
to wrap a call into an instance of an anonymous class (the worker object) where
the base class or implemented interface provides a method intended to do the work,
the worker object provides a concrete implementation of the worker method and specifically calls proceed() therein,
the worker method can be called right after object creation (as we will do here) or later, maybe even in its own thread,
the worker object may be passed around or added to a scheduling queue (none of which we will need here).
An elegant solution if you need to execute your proceed() calls asynchronously would be to create instances of anonymous Runnable classes. We will use our own abstract base class LogHelper, though, because we want some more sugar in our tea, specifically the option to pass a log message and some other parameters influencing log output to each worker. So this is what I did (package names and imports not shown in the sample code):
Abstract worker base class:
abstract class LogHelper {
// Object state needed for logging
String message;
boolean logDone;
boolean indent;
LogType type;
// Main constructor
LogHelper(String message, boolean logDone, boolean indent, LogType type) {
this.message = message;
this.logDone = logDone;
this.indent = indent;
this.type = type;
}
// Convenience constructors for frequent use cases
LogHelper(String message, boolean logDone) {
this(message, logDone, true, LogType.VERBOSE);
}
LogHelper(String message) {
this(message, true);
}
// Worker method to be overridden by each anonymous subclass
abstract void log();
}
Logging advice capturing execution of worker objects:
aspect LoggingAspect
{
void around(LogHelper logHelper) :
execution(* LogHelper.log()) && this(logHelper)
{
try {
SimpleLogger.log(logHelper.type, logHelper.message);
if (logHelper.indent)
SimpleLogger.indent();
proceed(logHelper);
} finally {
if (logHelper.indent)
SimpleLogger.dedent();
if (logHelper.logDone)
SimpleLogger.log(logHelper.type, logHelper.message + " - done");
}
}
// (...)
}
As you can see, the logging advice does some things before calling proceed(logHelper) (i.e. executing the worker object's log() method) and some things afterwards, using the state information stored inside the worker object, such as
message to be logged,
log level (here called "type"),
flag specifying if indentation level should be raised before proceeding,
flag specifying if "done" message should be printed after worker execution.
Because in my use case all logged methods return void, there is no need to implement return value passing, but this would be easily possible, if necessary. The advice's return value would then just be Object and we would pass the result of proceed() back to our caller, no big deal.
Some advice capturing joinpoints to be logged and utilising parametrised worker objects to get the work done:
aspect LoggingAspect
{
// (...)
pointcut processBook() : execution(* OpenbookCleaner.downloadAndCleanBook(Book));
pointcut download() : execution(* Downloader.download());
pointcut cleanBook() : execution(* OpenbookCleaner.cleanBook(Book));
pointcut cleanChapter() : execution(* OpenbookCleaner.cleanChapter(Book, File));
pointcut initialiseTitle() : execution(* *Filter.initialiseTitle(boolean));
void around(final Book book) : processBook() && args(book) {
new LogHelper("Book: " + book.unpackDirectory) {
void log() { proceed(book); } }.log();
}
void around() : download() {
new LogHelper("Downloading, verifying (MD5) and unpacking") {
void log() { proceed(); } }.log();
}
void around() : cleanBook() {
new LogHelper("Filtering") {
void log() { proceed(); } }.log();
}
void around(final File origFile) : cleanChapter() && args(*, origFile) {
new LogHelper("Chapter: " + origFile.getName()) {
void log() { proceed(origFile); } }.log();
}
void around() : initialiseTitle() {
new LogHelper("Initialising page title", false) {
void log() { proceed(); } }.log();
}
}
The examples show how you can
instantiate an anonymous LogHelper as a worker object with one or more constructor parameters, setting its state
implement the log() method, optionally using joinpoint state bound via this() or args(),
call/run the worker object (the call will be intercepted by the logging advice's pointcut and the real logging business be done there).