Skip to content

Commit

Permalink
Config alignment (#227)
Browse files Browse the repository at this point in the history
* 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).
  • Loading branch information
DariusIMP authored Sep 18, 2024
1 parent e8440c4 commit 4d4a7ce
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 1 deletion.
54 changes: 53 additions & 1 deletion zenoh-jni/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<Config> = Arc::from_raw(cfg_ptr);
let result = || -> Result<jstring> {
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.
Expand Down
48 changes: 48 additions & 0 deletions zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -267,6 +270,51 @@ class Config internal constructor(internal val jniConfig: JNIConfig) {
fun fromJsonElement(jsonElement: JsonElement): Result<Config> {
return JNIConfig.loadJsonConfig(jsonElement.toString())
}

/**
* Loads the configuration from the env variable [CONFIG_ENV].
*
* @return A result with the config.
*/
fun fromEnv(): Result<Config> = 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<String> {
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<Unit> {
return jniConfig.insertJson5(key, value)
}

protected fun finalize() {
Expand Down
17 changes: 17 additions & 0 deletions zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> = runCatching {
getJsonViaJNI(ptr, key)
}

fun insertJson5(key: String, value: String): Result<Unit> = runCatching {
insertJson5ViaJNI(this.ptr, key, value)
}
}
75 changes: 75 additions & 0 deletions zenoh-kotlin/src/commonTest/kotlin/io/zenoh/ConfigTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
}

0 comments on commit 4d4a7ce

Please sign in to comment.