From 4d4a7ce6c5333981f57ed765b19c1e8d7c75b375 Mon Sep 17 00:00:00 2001 From: Darius Maitia Date: Wed, 18 Sep 2024 18:18:08 +0200 Subject: [PATCH] Config alignment (#227) * Config: Adding client(peers) function to create a Client config. * Config: Adding peer() function to create a peer config. * Config: Adding fromEnv function to create a config. * Config: adding id() function. * Config: adding getJson(key) function * Config: adding insertJson5 function * Config: refactor * Config: test refactor * Config: make CONFIG_ENV const private. * Config: adding empty and default functions. * Config: removing 'peer()', 'default()', 'client(endpoints)' and 'empty()' after changes on the Rust API. * Cargo fmt * Config: removing `id()` function (unstable on Rust). --- zenoh-jni/src/config.rs | 54 ++++++++++++- .../src/commonMain/kotlin/io/zenoh/Config.kt | 48 ++++++++++++ .../kotlin/io/zenoh/jni/JNIConfig.kt | 17 +++++ .../commonTest/kotlin/io/zenoh/ConfigTest.kt | 75 +++++++++++++++++++ 4 files changed, 193 insertions(+), 1 deletion(-) diff --git a/zenoh-jni/src/config.rs b/zenoh-jni/src/config.rs index 0edf1420..6bd49d90 100644 --- a/zenoh-jni/src/config.rs +++ b/zenoh-jni/src/config.rs @@ -16,11 +16,12 @@ use std::{ptr::null, sync::Arc}; use jni::{ objects::{JClass, JString}, + sys::jstring, JNIEnv, }; use zenoh::Config; -use crate::errors::Result; +use crate::{errors::Result, jni_error}; use crate::{session_error, throw_exception, utils::decode_string}; /// Loads the default configuration, returning a raw pointer to it. @@ -119,6 +120,57 @@ pub extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_loadYamlConfigViaJN }) } +/// Returns the json value associated to the provided [key]. May throw an exception in case of failure, which must be handled +/// on the kotlin layer. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_getJsonViaJNI( + mut env: JNIEnv, + _class: JClass, + cfg_ptr: *const Config, + key: JString, +) -> jstring { + let arc_cfg: Arc = Arc::from_raw(cfg_ptr); + let result = || -> Result { + let key = decode_string(&mut env, &key)?; + let json = arc_cfg.get_json(&key).map_err(|err| session_error!(err))?; + let java_json = env.new_string(json).map_err(|err| jni_error!(err))?; + Ok(java_json.as_raw()) + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + JString::default().as_raw() + }); + std::mem::forget(arc_cfg); + result +} + +/// Inserts a json5 value associated to the provided [key]. May throw an exception in case of failure, which must be handled +/// on the kotlin layer. +#[no_mangle] +#[allow(non_snake_case)] +pub unsafe extern "C" fn Java_io_zenoh_jni_JNIConfig_00024Companion_insertJson5ViaJNI( + mut env: JNIEnv, + _class: JClass, + cfg_ptr: *const Config, + key: JString, + value: JString, +) { + || -> Result<()> { + let key = decode_string(&mut env, &key)?; + let value = decode_string(&mut env, &value)?; + let mut config = core::ptr::read(cfg_ptr); + let insert_result = config + .insert_json5(&key, &value) + .map_err(|err| session_error!(err)); + core::ptr::write(cfg_ptr as *mut _, config); + insert_result + }() + .unwrap_or_else(|err| { + throw_exception!(env, err); + }) +} + /// Frees the pointer to the config. The pointer should be valid and should have been obtained through /// one of the preceding `load` functions. This function should be called upon destruction of the kotlin /// Config instance. diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt index 90435cab..9271fedd 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt @@ -15,6 +15,7 @@ package io.zenoh import io.zenoh.jni.JNIConfig +import io.zenoh.protocol.ZenohID import java.io.File import java.nio.file.Path import kotlinx.serialization.json.JsonElement @@ -126,6 +127,8 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { companion object { + private const val CONFIG_ENV = "ZENOH_CONFIG" + /** * Returns the default config. */ @@ -267,6 +270,51 @@ class Config internal constructor(internal val jniConfig: JNIConfig) { fun fromJsonElement(jsonElement: JsonElement): Result { return JNIConfig.loadJsonConfig(jsonElement.toString()) } + + /** + * Loads the configuration from the env variable [CONFIG_ENV]. + * + * @return A result with the config. + */ + fun fromEnv(): Result = runCatching { + val envValue = System.getenv(CONFIG_ENV) + if (envValue != null) { + return fromFile(File(envValue)) + } else { + throw Exception("Couldn't load env variable: $CONFIG_ENV.") + } + } + } + + /** + * Returns the json value associated to the [key]. + */ + fun getJson(key: String): Result { + return jniConfig.getJson(key) + } + + /** + * Inserts a json5 value associated to the [key] into the Config. + * + * Example: + * ```kotlin + * val config = Config.default() + * + * // ... + * val scouting = """ + * { + * multicast: { + * enabled: true, + * } + * } + * """.trimIndent() + * config.insertJson5("scouting", scouting).getOrThrow() + * ``` + * + * @return A result with the status of the operation. + */ + fun insertJson5(key: String, value: String): Result { + return jniConfig.insertJson5(key, value) } protected fun finalize() { diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt index 0cd6be7b..7ca8e5c6 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt @@ -66,11 +66,28 @@ internal class JNIConfig(internal val ptr: Long) { @Throws private external fun loadYamlConfigViaJNI(rawConfig: String): Long + @Throws + private external fun getIdViaJNI(ptr: Long): ByteArray + + @Throws + private external fun insertJson5ViaJNI(ptr: Long, key: String, value: String): Long + /** Frees the underlying native config. */ private external fun freePtrViaJNI(ptr: Long) + + @Throws + private external fun getJsonViaJNI(ptr: Long, key: String): String } fun close() { freePtrViaJNI(ptr) } + + fun getJson(key: String): Result = runCatching { + getJsonViaJNI(ptr, key) + } + + fun insertJson5(key: String, value: String): Result = runCatching { + insertJson5ViaJNI(this.ptr, key, value) + } } diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ConfigTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ConfigTest.kt index 687e479a..241f8835 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ConfigTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ConfigTest.kt @@ -289,4 +289,79 @@ class ConfigTest { serverConfigFile.delete() } } + + @Test + fun `get json function test`() { + val jsonConfig = """ + { + mode: "peer", + connect: { + endpoints: ["tcp/localhost:7450"], + }, + scouting: { + multicast: { + enabled: false, + } + } + } + """.trimIndent() + + val config = Config.fromJson(jsonConfig).getOrThrow() + val value = config.getJson("connect").getOrThrow() + assertTrue(value.contains("\"endpoints\":[\"tcp/localhost:7450\"]")) + + val value2 = config.getJson("mode").getOrThrow() + assertEquals("\"peer\"", value2) + } + + @Test + fun `config should remain valid despite failing to get json value`() { + val jsonConfig = """ + { + mode: "peer", + connect: { + endpoints: ["tcp/localhost:7450"], + }, + scouting: { + multicast: { + enabled: false, + } + } + } + """.trimIndent() + + val config = Config.fromJson(jsonConfig).getOrThrow() + val result = config.getJson("non_existent_key") + assertTrue(result.isFailure) + + // We perform another operation and it should be ok + val mode = config.getJson("mode").getOrThrow() + assertEquals("\"peer\"", mode) + } + + @Test + fun `insert json5 function test`() { + val config = Config.default() + + val endpoints = """["tcp/8.8.8.8:8", "tcp/8.8.8.8:9"]""".trimIndent() + config.insertJson5("listen/endpoints", endpoints) + + val jsonValue = config.getJson("listen/endpoints").getOrThrow() + println(jsonValue) + assertTrue(jsonValue.contains("8.8.8.8")) + } + + @Test + fun `insert ill formatted json5 should fail and config should remain valid`() { + val config = Config.default() + + val illFormattedEndpoints = """["tcp/8.8.8.8:8"""".trimIndent() + val result = config.insertJson5("listen/endpoints", illFormattedEndpoints) + assertTrue(result.isFailure) + + val correctEndpoints = """["tcp/8.8.8.8:8", "tcp/8.8.8.8:9"]""".trimIndent() + config.insertJson5("listen/endpoints", correctEndpoints) + val retrievedEndpoints = config.getJson("listen/endpoints").getOrThrow() + assertTrue(retrievedEndpoints.contains("8.8.8.8")) + } }