Skip chained function call on certain result(s) - algorithm

I'm trying to implement a chain of function calls that could be expensive on their own, and I would like to only call the subsequent functions if the result of the previous satisfied the condition(s). For instance, I have these "models":
data class In(val a: Int, val c: String, val f: String, val t: String)
data class Out(val passed: Boolean, val code: String?)
...then this is the logic (never mind the variables/method names):
class Filter : SomeFilter {
override fun filter(input: In): Out {
return Stream.of(chML(input), chC(input), chA(input))
.filter { !it.passed }
.findFirst()
.orElseGet { Out(true, "SUCCESS") }
}
private fun chC(input: In): Out {
if (input.c !== "ADA") {
return Out(false, "INVALID_C")
}
return Out(true, "SUCCESS")
}
private fun chA(input: In): Out {
if (input.a >= 100) {
return Out(false, "INVALID_A")
}
return Out(true, "SUCCESS")
}
private fun chML(input: In): Out {
if (context.contains(input.f)) {
// context.add(input.to)
return Out(false, "INVALID_ML")
}
return Out(true, "SUCCESS")
}
}
The problem with these functions is that they should be expensive, so if the output from any of those is Out.passed === false, then I wouldn't like to call the next one because it could be interpreted as a terminal operation at that point.
Is there a way to implement this in such way without going the if/else-if route? The approach with streams is cleaner, but it does execute all the functions, regardless.

You can use sequence and yield
fun filter(input: In): Out {
return sequence {
yield(chML(input))
yield(chC(input))
yield(chA(input))
}
.firstOrNull { !it.passed }
?: Out(true, "SUCCESS")
}

You can use function references and invoke them in a map operation. Kotlin Sequences short-circuit based on any filters in the chain, including firstOrNull.
override fun filter(input: In): Out {
return sequenceOf(::chML, ::chC, ::chA)
.map { it(input) }
.firstOrNull { !it.passed }
?: Out(true, "SUCCESS")
}
By the way, I advise against using === or !== with Strings. That is checking reference equality which is very hard to use reliably unless all your source strings are private to this class so you can carefully make sure you know where/when they were instantiated and whether they were string literals. You should probably use != instead.

Related

Failing to use Mockitos thenReturn with predicate - thenReturn returns 404 instead of argument

I'm trying to use Mockito to return some default values in tests but I get a 404 on it
My test:
#Test
fun `Should return 200, when sending a valid push notification`() {
// Arrange
Mockito.`when`(subscriptionStore.getSubscription{ it.peerID == validSubscription.peerID})
.thenReturn(
validSubscription
)
// Act
val response = mockMvc.post("/push") {
contentType = MediaType.APPLICATION_JSON
content = objectMapper.writeValueAsString(validPushMessage)
}
// Assert
response.andDo { print() }
.andExpect {
status { isOk() }
}
}
and here's the method on the interface I try to mock:
interface SubscriptionStore {
fun addSubscription(newSubscription: Subscription)
fun getSubscriptions(): Collection<Subscription>
fun getSubscription(predicate: (Subscription) -> Boolean): Subscription?
fun deleteSubscription(peerID: String)
fun updateSubscription(subscription: Subscription)
class DuplicateElementException(msg: String) : Exception(msg)
}
and here's the usage of the mocked method that doesn't return what I told it but gives me 404:
override fun push(pushMessage: PushMessage) {
val recipientSubscription = subscribeService.getSubscription(pushMessage.recipient)
?: throw NoSuchElementException("Recipient not found")
}
which calls this from my subscriptionStore
override fun getSubscription(PeerID: String): Subscription? = subscriptionStore.getSubscription { it.peerID == PeerID}
In Kotlin, 2 different lambdas with identical code are not considered equal:
val fun1: (Int) -> Boolean = {it > 5}
val fun2: (Int) -> Boolean = {it > 5}
println(fun1 == fun2) // false
This is why your stubbing fails - you pass different lambda in your test, and a different one in the actual code
To answer the original post: I would probably relax stubbing requirements on the predicate and use the ArgumentMatchers.any argument matcher
On top of that - selection of item by ID is typically exposed by DBs as a separate operation, as it is the fastest way to reach the element. Maybe it is worth adding to your API as well?

How to place a conditional check inside springboot project reactor Mono stream (written in Kotlin)?

Pretty new to project reactor here, I am struggling to put a conditional check inside my Mono stream. This part of my application is receiving an object from Kafka. Let's say the object is like this.
data class SomeEvent(val id: String, val type: String)
I have a function that handles this object like this.
fun process(someEvent: SomeEvent): Mono<String> {
val id = someEvent.id
val checkCondition = someEvent.type == "thisType"
return repoOne.getItem(id)
.map {item ->
// WHAT DO I DO HERE TO PUT A CONDITIONAL CHECK
createEntryForItem(item)
}
.flatMap {entry ->
apiService.sendEntry(entry)
}
.flatMap {
it.bodyToMono(String::class.java)
}
.flatMap {body ->
Mono.just(body)
}
}
So, what I want to do is check whether checkCondition is true and if it is, I want to call a function repoTwo.getDetails(id) that returns a Mono<Details>.
createEntryForItem returns an object of type Entry
apiService.sendEntry(entry) returns a Mono<ClientResponse>
It'd be something like this (in my mind).
fun process(someEvent: SomeEvent): Mono<String> {
val id = someEvent.id
val checkCondition = someEvent.type == "thisType"
return repoOne.getItem(id)
.map {item ->
if (checkCondition) {
repoTwo.getDetails(id).map {details ->
createEntryForItem(item, details)
}
} else {
createEntryForItem(item)
}
}
.flatMap {entry ->
apiService.sendEntry(entry)
}
.flatMap {
it.bodyToMono(String::class.java)
}
.flatMap {body ->
Mono.just(body)
}
}
But, obviously, this does not work because the expression inside the if statement is cast to Any.
How should I write it to achieve what I want to achieve?
UPDATED: The location of where I like to have the conditional check.
You should use flatMap() and not map() after getItem().
return repoOne.getItem(id)
.flatMap {item ->
if (checkCondition) {
repoTwo.getDetails(id).map {details ->
createEntryForItem(item, details)
}
} else {
Mono.just(createEntryForItem(item))
}
}
In a map{} you can transform the value. Because you want to call getDetails() (which returns a reactive type and not a value) to do that you have to use flatMap{}. And that's why you need to wrap your item in a Mono by calling Mono.just(createEntryForItem(item)) on the else branch.
Just split it to another function. Your code will be cleaner too.
repoOne.getItem(id)
.map { createEntry(it, checkCondition) }.
.flatMap.....
private fun createEntry(item, checkCondition): Item {
return if (checkCondition) {
repoTwo.getDetails(id).map { createEntryForItem(item, it) }
} else {
createEntryForItem(item)
}
}

WebFlux functional: How to detect an empty Flux and return 404?

I'm having the following simplified handler function (Spring WebFlux and the functional API using Kotlin). However, I need a hint how to detect an empty Flux and then use noContent() for 404, when the Flux is empty.
fun findByLastname(request: ServerRequest): Mono<ServerResponse> {
val lastnameOpt = request.queryParam("lastname")
val customerFlux = if (lastnameOpt.isPresent) {
service.findByLastname(lastnameOpt.get())
} else {
service.findAll()
}
// How can I detect an empty Flux and then invoke noContent() ?
return ok().body(customerFlux, Customer::class.java)
}
From a Mono:
return customerMono
.flatMap(c -> ok().body(BodyInserters.fromObject(c)))
.switchIfEmpty(notFound().build());
From a Flux:
return customerFlux
.collectList()
.flatMap(l -> {
if(l.isEmpty()) {
return notFound().build();
}
else {
return ok().body(BodyInserters.fromObject(l)));
}
});
Note that collectList buffers data in memory, so this might not be the best choice for big lists. There might be a better way to solve this.
Use Flux.hasElements() : Mono<Boolean> function:
return customerFlux.hasElements()
.flatMap {
if (it) ok().body(customerFlux)
else noContent().build()
}
In addition to the solution of Brian, if you are not want to do an empty check of the list all the time, you could create a extension function:
fun <R> Flux<R>.collectListOrEmpty(): Mono<List<R>> = this.collectList().flatMap {
val result = if (it.isEmpty()) {
Mono.empty()
} else {
Mono.just(it)
}
result
}
And call it like you do it for the Mono:
return customerFlux().collectListOrEmpty()
.switchIfEmpty(notFound().build())
.flatMap(c -> ok().body(BodyInserters.fromObject(c)))
I'm not sure why no one is talking about using the hasElements() function of Flux.java which would return a Mono.

How to write a reusable transform for String to Enum value across a group of Enum classes? (Kotlin)

I have a group >5 of Enum classes that take String parameter in its values, and I want to have simple code for all these Enum classes to convert from a String field in JSON object.
enum class Religiousness(val jsonStr: String, val resID: Int) {
NotAtAll("none", R.string.not_religious),
Somewhat("somewhat", R.string.somewhat_religious),
Very("very", R.string.very_religious),
;
override fun toString() = jsonStr
fun displayString(res: Resources) = res.getString(resID)
}
I want to be able to write code like this
fun JsonConvertStrToEnum(enumClass: Class<Enum<*>>, str: String): Enum<*> {
for (enumval in enumClass.enumConstants) {
if ((enumval as IJsonStringConvertible).jsonStr() == str)
return enumval
}
throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}")
}
I am having a hard time figuring out if IJsonStringConvertible can work, and what its definition would be, and how to implement it in the Enum value instances. Any advice?
Update: I have now written the converter as this. Is this the best way? Can I also express that the return value is a subtype of the parameter so don't need to cast return value?
fun JsonConvertStrToEnum(enumClass: Class<out Enum<*>>, str: String): Enum<*> {
for (enumval in enumClass.enumConstants) {
if (enumval.toString() == str)
return enumval
}
throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}")
}
Enums as other classes can implement interfaces like so:
interface IJsonStringConvertible {
val jsonStr:String
}
enum class Religiousness(override val jsonStr: String, val resID: Int) : IJsonStringConvertible {
NotAtAll("none", R.string.not_religious),
Somewhat("somewhat", R.string.somewhat_religious),
Very("very", R.string.very_religious),
;
override fun toString() = jsonStr
fun displayString(res: Resources) = res.getString(resID)
}
Which would then be used as:
for (enumval in enumClass.enumConstants) {
if ((enumval as IJsonStringConvertible).jsonStr == str)
return enumval
}
However the above lookup can be expensive (if used millions of times). Take a look at the reverse lookup question to find out how to do it more efficiently.
If it helps anyone, here's the final version in my production app.
fun <EnumT : Enum<EnumT>> ConvertStrToEnum(enumClass: Class<EnumT>, str: String?): EnumT? {
if (str == null)
return null
for (enumval in enumClass.enumConstants) {
if (enumval.toString() == str)
return enumval
}
throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}")
}
fun <EnumT : Enum<EnumT> > ConvertStrArrayToEnumSet(enumClass: Class<EnumT>, array: List<String>?) : EnumSet<EnumT> {
val set = EnumSet.noneOf(enumClass)
array?.forEach { value -> set.add(ConvertStrToEnum(enumClass, value)) }
return set
}

How to populate Enum from the values retrieved from Database

Looking at the example here at Message Controller for Pizza Example, if I want to populate Size or Kind based on some user input and make a call to the database, how would I do that?
So far as I know, there is not an easy way to populate the Enum at runtime.
It looks like this hasn't been implemented yet. I took a look inside https://github.com/Microsoft/BotBuilder/blob/master/CSharp/Library/FormFlow/FormBuilder.cs and found this:
internal static void TypePaths(Type type, string path, List<string> paths)
{
if (type.IsClass)
{
if (type == typeof(string))
{
paths.Add(path);
}
else if (type.IsIEnumerable())
{
var elt = type.GetGenericElementType();
if (elt.IsEnum)
{
paths.Add(path);
}
else
{
// TODO: What to do about enumerations of things other than enums?
}
}
else
{
FieldPaths(type, path, paths);
}
}
else if (type.IsEnum)
{
paths.Add(path);
}
else if (type == typeof(bool))
{
paths.Add(path);
}
else if (type.IsIntegral())
{
paths.Add(path);
}
else if (type.IsDouble())
{
paths.Add(path);
}
else if (type.IsNullable() && type.IsValueType)
{
paths.Add(path);
}
else if (type == typeof(DateTime))
{
paths.Add(path);
}
}
Notice the TODO about enumerations other than enums.
Outside of the FormBuilder we can use PromptDialog.Choice which takes an IEnumerable<> of your options.
It is possible to chain dialogs together, so you may have to split your FormDialog into two with the PromptDialog in-between.
Alternatively take a fork of BotBuilder and implement the TODO!

Resources