Javalin 4: How to write properly check in call to Validator.check()? - kotlin

I am trying to adopt existing code of parameter validation from Javalin 3 to Javalin 4. It uses Javalin's Validator class. Here's the code I've got (rewritten):
val messageId = ctx.pathParamAsClass<String>("messageId")
.check(check = { it.trim().matches(Uuid.REGEX) }, errorMessage = "message id must be a UUID")
.get()
.trim()
And I am getting compile error for the check() call:
e:
/home/ivan/.../SourceFile.kt: (53, 6): None of the following functions can be called with the arguments
supplied:
public final fun check(check: Check<String> /* = (String) -> Boolean */, error: ValidationError<String>):
Validator<String> defined in io.javalin.core.validation.Validator
public final fun check(check: Check<String> /* = (String) -> Boolean */, error: String): Validator<String> defined in
io.javalin.core.validation.Validator
I can't understand why there is an error. I assume I should have matched second overload of check(). How to write it correctly?
Note: I have read Javalin 3 to 4 migration guide, which gives example like this:
ctx.queryParamAsClass<Int>("age")
.check({ it >= 18 }, ValidationError("AGE_TOO_LOW", args = mapOf("minAge" to 18)))
.get()
which I seem to follow, except I give it error message as string, but there's matching overload. So what is wrong here?

The cause was that second parameter of check() is called error, not errorMessage, i.e. correct code is like this:
...
.check(check = { it.trim().matches(Uuid.REGEX) }, error = "...")
...

Related

TypeVariable(V) Required in Mutable Map assignment in Kotlin. Cannot put the value from a map to itself

The following example is a simplified version of what I am trying to do in my application.
fun main() {
val test: MutableMap<Int, String> = mutableMapOf(
1 to "Apple",
)
test[2] = test[1] // test[1] has incorrect type.
}
The code doesn't compile. IntelliJ gives the following hint:
Type mismatch.
Required: TypeVariable(V)
Found: String?
I don't understand what a TypeVariable is. but when I provide a default value the error disappears
test[2] = test[1] ?: "Grape"
Why the required type is TypeVariable(V), not String, and what exactly is it? What to do if there's no default value for my application purposes?
... = test[1]
returns a String? as the hint showed you. But test is a MutableMap of <Int, String>, which means you need to assign a String:
... = test[1]!!
Of course this will only work if 1 is a valid key in test. Otherwise your code with the null safety operator is the way to go:
... = test[1] ?: "default value"

R2DBC: How to bind data class for sql query without needing all parameters?

I am trying to bind my data class for a sql query but I am getting a error when I am not using all the parameters from my data class. Is there a way to check in the sql query which parameters needs binding and which ones do not or allow to bind parameters which are not used. The error looks as following:
Request error
java.lang.IllegalArgumentException: Identifier 'deleted_at' is not a valid identifier. Should be of the pattern '\$([\d]+)'.
at io.r2dbc.postgresql.ExtendedQueryPostgresqlStatement.getIndex(ExtendedQueryPostgresqlStatement.java:196)
Caused by: java.lang.IllegalArgumentException: Identifier 'deleted_at' is not a valid identifier. Should be of the pattern '\$([\d]+)'.
at io.r2dbc.postgresql.ExtendedQueryPostgresqlStatement.getIndex(ExtendedQueryPostgresqlStatement.java:196)
And this is the code I use:
Repository:
client
.sql(
"""
INSERT INTO app_user_settings (uuid, allows_to_process_transactions, app_user_id) VALUES (:uuid, :allows_to_process_transactions, :app_user_id)
RETURNING *
""".trimIndent()
)
.bind(AppUserSettingsWriteConverter(), appUserSettings)
.map(AppUserSettingsReadConverter()::convert)
.awaitOne()
Custom bind method:
fun <T> DatabaseClient.GenericExecuteSpec.bind(
convertor: Converter<T, OutboundRow>,
value: T
): DatabaseClient.GenericExecuteSpec {
val outboundRow = convertor.convert(value!!)!!
val t = outboundRow.toMap()
var execution = this
t.forEach { (t, u) ->
execution = execution.bind(t.toString(), u)
}
return execution
}
WriteConverter:
class AppUserSettingsWriteConverter : Converter<AppUserSettings, OutboundRow> {
override fun convert(source: AppUserSettings): OutboundRow {
val outboundRow = OutboundRow()
if (source.isSaved()) {
outboundRow[SqlIdentifier.unquoted("id")] = Parameter.from(source.id)
}
outboundRow[SqlIdentifier.unquoted("uuid")] = Parameter.from(source.uuid)
outboundRow[SqlIdentifier.unquoted("allows_to_process_transactions")] = Parameter.from(source.allowsToProcessTransactions)
outboundRow[SqlIdentifier.unquoted("app_user_id")] = Parameter.from(source.appUserId)
outboundRow[SqlIdentifier.unquoted("deleted_at")] = Parameter.fromOrEmpty(source.deletedAt, ZonedDateTime::class.java)
return outboundRow
}
}
I am using now a check if deleted_at is empty and then not bind it but would prefer if there is another way to do it.

Zip 2 lists of Observable with different return types

I'd like to wait until all the data from the API to be downloaded successfully and then do some operations on it.
The data result from observablesAPI1 and observablesAPI2 are different.
val observablesAPI1:List<Single<ApiResponse1>? = idApi1List.map{api1Repository.getData(it)}
val observablesAPI2:List<Single<ApiResponse2>? = idApi2List.map{api2Repository.getData(it)}
// this is not working
Single.zip(observablesAPI1,observablesAPI2,BiFunction <List<ApiResponse1>, List<ApiResponse2>> { apiResultList1, apiResultList2 -> // operations}
I thought about using nested zips but I'm not sure if it's possible to do that.
Edit:
There is actually 2 errors
when hover on observablesAPI1(similiar error on observablesAPI2):
Type missmatch. Required: SingleSource!>!
Found: List>?
when hover on BiFunction:
3 type arguments expected for fun
BiFunction(function:(t1: T1, t2: T2) ->R):BiFunction))
I would like to suggest you change the way which you map ids to data.
val observablesAPI1 = Observable.fromIterable(idApi1List)
.flatMapSingle { id -> api1Repository.getData(id) }
.toList()
val observablesAPI2 = Observable.fromIterable(idApi2List)
.flatMapSingle { id -> api2Repository.getData(id) }
.toList()
Single.zip(observablesAPI1, observablesAPI2, BiFunction<List<ApiResponse1>, List<ApiResponse2>, String> { list1, list2 ->
//do something
}).subscribe()
Note, that in my solution in zip function you have just two easy maintainable lists of ApiResponse1 and ApiResponse2.
It looks like you are having compilation errors.
This is from the javadocs of BiFunction:
/**
* A functional interface (callback) that computes a value based on multiple input values.
* #param <T1> the first value type
* #param <T2> the second value type
* #param <R> the result type
*/
public interface BiFunction<T1, T2, R> {
Your observablesAPI1 and observablesAPI2 have the type List<Single<ApiResponse*> but you are writing List<ApiResponse*>.
You are also missing the result type. For example, if you want to return a String, this is how your code should look like:
Single.zip(
observablesAPI1,
observablesAPI2,
BiFunction<List<Single<ApiResponse1>, List<Single<ApiResponse2>, String> {
apiResultList1, apiResultList2 -> "my result!"
})

kotlin with fastjson parse object error: default constructor not found

I am trying use fastjson parse object in Kotlin code. but exception happened when I use JSON.parseObject, here are detail:
My data class:
import com.alibaba.fastjson.JSONObject
data class StatesMessage #JvmOverloads constructor(val command: String =
"states", var states: States = States()) {
fun toJsonString(): String {
return JSONObject.toJSONString(this)
}
data class States(var x: Double = 0.0, var y: Double = 0.0)
}
Then I try to get object from string:
val state = JSON.parseObject(s, StatesMessage::class.java)
But exception throw from fastjson:
Caused by: com.alibaba.fastjson.JSONException: default constructor not found.
class com.example.demo.StatesMessage
at com.alibaba.fastjson.util.JavaBeanInfo.build(JavaBeanInfo.java:475)
at com.alibaba.fastjson.util.JavaBeanInfo.build(JavaBeanInfo.java:221)
at com.alibaba.fastjson.parser.ParserConfig.createJavaBeanDeserializer(ParserConfig.java:670)
at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:587)
at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:398)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:665)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:365)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:269)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:488)
at com.example.demo.StartupRunner.run(StartupRunner.kt:25)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:813)
... 5 more
all code refer to https://github.com/forest-yang/koltinjson
I think it's a fastjson (till 1.2.54) bug.
When I change to gson, it's work.
/* it will throw exception
val state = JSON.parseObject(s, StatesMessage::class.java)
*/
val state = Gson().fromJson(s, StatesMessage::class.java)
logger.info(state.states.x)
logger.info(state.states.y)

passing var-args to MessageFormat.format in kotlin

I am trying to parse errorCode like
4011=Error thrown expected: {0} found: {1}.
using Message.format in kotlin
loggingService.logTheMsg("4011", arrayOf("${expectedVal}", "${actualVal}"))
In logTheMsg I am using this code :
var errorMessage = props.getProperty(errorCode)
errorMessage = MessageFormat.format(errorMessage as String, args)
println("${errorMessage}.")
but getting output as :
Error thrown expected:[Ljava.lang.String;#38f3b4ba found: {1}.
It might help in answering , the same thing is achieved in java like this:
parse(value, new String[]{"firstName","lastName"});
And in parse:
parse(String value, String[]args) {
value = MessageFormat.format((String) value, args);
System.out.println(value);
}
prints:
my name is firstName lastName
To remove ambiguity, Kotlin requires 'spread operator' (*) on array which is going to be passed as vararg, i. e.
loggingService.logTheMsg("4011", *arrayOf("${expectedVal}", "${actualVal}"))
Also, "${expectedVal}" should be replaced with expectedVal:
loggingService.logTheMsg("4011", *arrayOf(expectedVal, actualVal))
And, of course, you can use varargs as they intended to be used:
loggingService.logTheMsg("4011", expectedVal, actualVal)