gRPC Retry using someApi.withOption - kotlin

Is it possible to use .withOption to add retryPolicy configurations to a grpc client stub? I've tried something like below but it seems it fails to equals on the key. Is there a better way of doing this please?
val someApi: OfferTokenAPIGrpcKt.OfferTokenAPICoroutineStub
someApi.withOption(CallOptions.Key.create("io.grpc.internal.ManagedChannelServiceConfig.MethodInfo"), createMethodInfo())
.randomGrpcEndpoint()
fun createMethodInfo(): Map<String, Map<String, Any>> {
val statusCodes = listOf("UNAVAILABLE", "UNKNOWN")
val retryPolicyMap = mapOf(
"maxAttempts" to "5",
"initialBackoff" to "2s",
"maxBackoff" to "30s",
"backoffMultiplier" to "2",
"retryableStatusCodes" to statusCodes,
)
val methodConfig = mapOf("retryPolicy" to retryPolicyMap)
return methodConfig

No, you cannot specify the retry policy per-RPC. CallOptions.Key use reference equality, so simply creating a key with the same name does nothing other than reuse the debug string.
You can specify a service config via ManagedChannelbuilder.defaultServiceConfig(). That allows you to configure a method's retry policy, but that does not allow you to change the settings per-RPC.

Related

uuidconfigurationexception in kmogo Kotlin

import org.litote.kmongo.KMongo
fun main() {
val client = Kmongo.createClient(/* connection string from mongodb */)
val database = client.getDatabase(/* databaseName */)
}
my code ^
this is what it returns:
Exception in thread "main" org.bson.codecs.configuration.CodecConfigurationException: Changing the default UuidRepresentation requires a CodecRegistry that also implements the CodecProvider interface
at org.litote.kmongo.KMongo.createRegistry(KMongo.kt:89)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:78)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:60)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:50)
at MainKt.main(Main.kt:3)
at MainKt.main(Main.kt)
for security purposes, i have omitted the additional code that are irrelevant and the database names
Thanks for your help in advance!
From kmongo source here: https://github.com/Litote/kmongo/blob/master/kmongo-core/src/main/kotlin/org/litote/kmongo/KMongo.kt, it seems that this is happening because the UUID Representation is JAVA_LEGACY.
Hence, you should supply a MongoClientSettings object to createClient, which specifies a UUID representation other than JAVA_LEGACY. Since UuidRepresentation is an enum (https://mongodb.github.io/mongo-java-driver/3.5/javadoc/org/bson/UuidRepresentation.html), you can try using STANDARD.
Like this:
val settings = MongoClientSettings.builder()
.applyConnectionString(ConnectionString("Connection String here"))
.uuidRepresentation(UuidRepresentation.STANDARD)
.codecRegistry(KMongoUtil.defaultCodecRegistry)
.build()
val client = KMongo.createClient(settings)
// other code below...
This should get KMongo/MongoDB to use UuidRepresentation.STANDARD instead of UuidRepresentation.JAVA_LEGACY (in which KMongo will always throw an exception)
Thanks!

Configure array/object indentation for YAML in Jackson

I'm trying to generate a YAML file from an input Map I'm using Jackson and the YamlFactory utility provided by Jackson to do so. I'm attempting to configure the indentation property for the YAML output, but it doesn't seem like that's being respected at all.
Here's how my code looks like:
fun getSdkResultAsGenericObject(sdkResult: Any?): Any? {
if (sdkResult == null) {
return null
}
var genericObj: Any?
val stringified = genericSdkObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(sdkResult)
.replace("\n", "")
val isArray = stringified.startsWith("[")
genericObj = if (isArray) {
genericSdkObjectMapper.readValue(stringified, List::class.java)
} else {
genericSdkObjectMapper.readValue(stringified, LinkedHashMap::class.java)
}
val defaultYaml = resultYamlMapper.writerWithDefaultPrettyPrinter().writeValueAsString(genericObj )
}
The declaration of the resultYamlMapper is like this:
val yamlFactory = YAMLFactory()
.configure(YAMLGenerator.Feature.SPLIT_LINES, false)
.configure(YAMLGenerator.Feature.INDENT_ARRAYS, true)
val resultYamlMapper = ObjectMapper(YamlFactory())
The documentation says that the INDENT_ARRAYS feature uses 2 spaces by default. I'm trying to understand how I can configure that? I need 4 spaces in the resultant YAML. I tried setting a pretty print writer:
val yamlFactory = YAMLFactory()
.configure(YAMLGenerator.Feature.SPLIT_LINES, false)
.configure(YAMLGenerator.Feature.INDENT_ARRAYS, true)
val resultYamlMapper = ObjectMapper(YamlFactory())
val arrayIndenter = DefaultIndenter(" ", DefaultIndenter.SYS_LF)
val objectIndenter = DefaultIndenter(" ", DefaultIndenter.SYS_LF)
resultYamlMapper.setDefaultPrettyPrinter(DefaultPrettyPrinter().withObjectIndenter(objectIndenter).withArrayIndenter(arrayIndenter))
But this doesn't seem to be respected at all. Any thoughts? Or does Jackson not let you configure the indentation at all?
The docs show that the PrettyPrinter interface is only for the JsonGenerator.
If you want to customize your YAML output, you have to use the SnakeYAML API directly (which is used by Jackson for YAML processing). SnakeYAML has similar features to Jackson and there is little reason to use Jackson if you only want to process YAML. Most importantly, it lets you configure YAML formatting.

How do you mock a URL connection in Kotlin?

I've seen a lot of examples on how to mock a connection in Java but haven't seen any explaining how to do it in Kotlin. A bit of code that I want mocked as an example:
val url = URL("https://google.ca")
val conn = url.openConnection() as HttpURLConnection
with(conn) {
//doStuff
}
conn.disconnect()
Similar to a question like this but for Kotlin:
how to mock a URL connection
Kotlin and Java can interop with one another, so you should be able to take your exact example (from the question) provided and convert it to Kotlin (or don't convert it and call the Java directly):
#Throws(Exception::class)
fun function() {
val r = RuleEngineUtil()
val u = PowerMockito.mock(URL::class.java)
val url = "http://www.sdsgle.com"
PowerMockito.whenNew(URL::class.java).withArguments(url).thenReturn(u)
val huc = PowerMockito.mock(HttpURLConnection::class.java)
PowerMockito.`when`(u.openConnection()).thenReturn(huc)
PowerMockito.`when`(huc.getResponseCode()).thenReturn(200)
assertTrue(r.isUrlAccessible(url))
}
It's worth noting that you should probably consider using an actual mocking HTTP server like HttpMocker for handling this as opposed to implement the behavior yourself.

Micronaut declarative client with base url per environment

I'd like to be able to use Micronaut's declarative client to hit an a different endpoint based on whether I'm in a local development environment vs a production environment.
I'm setting my client's base uri in application.dev.yml:
myserviceclient:
baseUri: http://localhost:1080/endpoint
Reading the docs from Micronaut, they have the developer jumping through quite a few hoops to get a dynamic value piped into the actual client. They're actually quite confusing. So I've created a configuration like this:
#ConfigurationProperties(PREFIX)
class MyServiceClientConfig {
companion object {
const val PREFIX = "myserviceclient"
const val BASE_URL = "http://localhost:1080/endpoint"
}
var baseUri: String? = null
fun toMap(): MutableMap<String, Any> {
val m = HashMap<String, Any>()
if (baseUri != null) {
m["baseUri"] = baseUri!!
}
return m
}
}
But as you can see, that's not actually reading any values from application.yml, it's simply setting a const value as a static on the class. I'd like that BASE_URL value to be dynamic based on which environment I'm in.
To use this class, I've created a declarative client like this:
#Client(MyServiceClientConfig.BASE_URL)
interface MyServiceClient {
#Post("/user/kfc")
#Produces("application/json")
fun sendUserKfc(transactionDto: TransactionDto)
}
The docs show an example where they're interpolating values from the config map that's built like this:
#Get("/api/\${bintray.apiversion}/repos/\${bintray.organization}/\${bintray.repository}/packages")
But how would I make this work in the #Client() annotation?
Nowhere in that example do they show how bintray is getting defined/injected/etc. This appears to be the same syntax that's used with the #Value() annotation. I've tried using that as well, but every value I try to use ends up being null.
This is very frustrating, but I'm sure I'm missing a key piece that will make this all work.
I'm setting my client's base uri in application.dev.yml
You probably want application-dev.yml.
But how would I make this work in the #Client() annotation?
You can put a config key in the #Client value using something like #Client("${myserviceclient.baseUri}").
If you want the url somewhere in your code use this:
#Value("${micronaut.http.services.occupancy.urls}")
private String occupancyUrl;

HOCON config file dynamic substitution

I'm using HOCON to configure log messages and I'm looking for a way to substitute placeholder values dynamically.
I know that ${?PLACEHOLDER} will read an environment variable and returns an empty string when the PLACEHOLDER environment variable doesn't exist.
Example
This is an example of what I had in mind:
(I'm using config4k to load HOCON )
data class LogMessage(val message: String, val code: String)
fun getMessage(key: String, placeholderValues: Array<String> = arrayOf()): LogMessage {
val config = ConfigFactory.parseString("MY_LOG_MESSAGE {code = ABC-123456, message = My log message with dynamic value %0% and another dynamic value %1% }")
val messageObject = config.extract<LogMessage>(key)
var message = messageObject.message
placeholderValues.forEachIndexed { placeholderIndex, value ->
message = message.replace("%$placeholderIndex%", value)
}
return messageObject.copy(message = message)
}
fun main(args: Array<String>) {
println(getMessage("MY_LOG_MESSAGE", arrayOf("value 1", "value 2")))
// prints: LogMessage(message=My log message with dynamic value value 1 and another dynamic value value 2, code=ABC-123456)
}
Even though this works, it doesn't look like the best solution and I assume there already is a solution for this.
Could someone tell me if there is a built-in solution?
First things first.
HOCON is just a glorified JSON format.
config4k is just a wrapper.
All your work is being done by Typesafe Config, as you've probably noticed.
And judging by their documentation and code, they support placeholders only from withing the file, or from the environment:
This library limits itself to config files. If you want to load config
from a database or something, you would need to write some custom
code.
But for what you're doing, simple String.format() should be enough:
fun interpolate(message: String, vararg args: Any) = String.format(message, *args)
println(interpolate("My message was %s %s %s %s", "a", 1, 3.32, true))
// My message was a 1 3.32 true
Notice that you can use * to destructure your array.