Crates.io | elektra |
lib.rs | elektra |
version | 0.11.1 |
source | src |
created_at | 2019-09-18 15:17:15.007401 |
updated_at | 2023-08-21 04:36:10.727734 |
description | Elektra serves as a universal and secure framework to access configuration parameters in a global, hierarchical key database. |
homepage | https://www.libelektra.org/ |
repository | https://master.libelektra.org/src/bindings/rust |
max_upload_size | |
id | 165762 |
size | 153,407 |
Elektra serves as a universal and secure framework to access configuration parameters in a global, hierarchical key database.
For more information about Elektra itself, visit the website.
Depending on how you installed libelektra, you should use different ways to get the bindings. If you installed it with a package manager, you should use the crates from crates.io. If you built libelektra locally, you should use the bindings that are built in the build
directory.
If you installed elektra via a package manager, you should use the elektra crate or elektra-sys crate if you need the raw bindings. In this case you will need libelektra
itself, as well as the development headers (often called libelektra-dev
) for bindings generation.
The elektra-sys
, as well as the elektra
crate have a feature called pkg-config
that you can enable to find the installation of elektra and its headers. It is not enabled by default, but recommended and you can do so by adding features = ["pkg-config"]
to the dependency section as seen below. The pkg-config
utility has to be installed then. Your Cargo.toml dependencies might thus look like this
[dependencies]
elektra = { version = "0.9.10", features = ["pkg-config"] }
# Directly depending on elektra-sys is only needed if you need to use the raw bindings
elektra-sys = { version = "0.9.10", features = ["pkg-config"] }
If you don't use the pkg-config
feature, the build script will look for the Elektra installation in /usr/local/include/elektra
and /usr/include/elektra
.
With this in place, the bindings should be built when you run cargo build
.
To build the bindings explicitly as part of the Elektra build process, we add the option rust
to -DBINDINGS
. Now build libelektra and the bindings will be built as part of this process.
Your Cargo.toml dependencies might then look like this
[dependencies]
elektra = { path = "../libelektra/build/src/bindings/rust/elektra/"}
Note that your dynamic linker must be able to find libelektra-{core,meta,kdb}
. If you just compiled it, you can run source ../scripts/dev/run_env
from the build
directory to modify your PATH
appropriately.
See the example
directory for a fully setup project. To run it, change directories into build/src/bindings/rust/example/
and run cargo run --bin key
.
To start with a new project, use cargo new elektra_rust
. Now add the elektra
crate to the dependencies. The crate is in the src/bindings/rust
subdirectory of your build
directory, so the exact paths depends on your system. Change the paths (and possibly version) appropriately and add the following dependencies to your Cargo.toml
.
[dependencies]
elektra = { version = "0.9.10", path = "~/git/libelektra/build/src/bindings/rust/elektra" }
# Directly depending on elektra-sys is only needed if you need to use the raw bindings
elektra-sys = { version = "0.9.10", path = "~/git/libelektra/build/src/bindings/rust/elektra-sys" }
If you run cargo run
and everything builds correctly and prints Hello, world!
, you can replace the contents of main.rs
with the examples shown in the next section.
An example for using a StringKey
. Run it from the example
directory using cargo run --bin key
. See the full example for more.
extern crate elektra;
use elektra::{ReadableKey, StringKey, WriteableKey};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// To create a simple key with a name and value
let mut key = StringKey::new("user:/test/language")?;
key.set_value("rust");
println!("Key with name {} has value {}", key.name(), key.value());
Ok(())
}
Compared to the C-API, there are two distinct key types, StringKey
and BinaryKey
. With these, type mismatches such as calling keyString
on a BinaryKey
is not possible. The only difference between them is the type of value you can set and get from them. They are only wrappers over the Key
from the C-API.
Use a BinaryKey
for setting arbitrary byte values.
extern crate elektra;
use elektra::{BinaryKey, ReadableKey, WriteableKey};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let binary_content: [u8; 7] = [25, 34, 0, 254, 1, 0, 7];
let mut key = BinaryKey::new("user:/test/rust")?;
key.set_value(&binary_content);
let read_content = key.value();
println!(
"Key with name {} holds bytes {:?}",
key.name(),
read_content
);
Ok(())
}
The functionality of the keys is split into two traits, ReadableKey
and WritableKey
, which define methods that only read information from a key, and modify a key, respectively. For example, the method to retrieve metakeys only returns a key that implements ReadableKey
, which is one of the keys in a ReadOnly
wrapper.
A KeySet is a set of StringKeys.
new
or preallocate space for a number of keys with with_capacity
.Iterator
trait, so you can iterate immutably or mutably.See the full example for more. Run it from the example
directory using cargo run --bin keyset
.
extern crate elektra;
use elektra::{KeyBuilder, KeySet, ReadableKey, StringKey, keyset};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// keyset! works just like vec!
let keyset = keyset![
KeyBuilder::<StringKey>::new("user:/sw/app/#1/host")?
.value("localhost")
.build(),
KeyBuilder::<StringKey>::new("user:/sw/app/#1/port")?
.value("8080")
.build(),
];
// Iterate the keyset
for key in keyset.iter() {
println!("Key ({}, {})", key.name(), key.value());
}
Ok(())
}
A KeySet
only contains StringKey
s, since they are far more prevalent than BinaryKey
s. However since the underlying KeySet holds generic Key
s, BinaryKey
s can occur. You can cast between the two keys, by using the From
trait. This is safe memory-wise, but can be unsafe if you cast a BinaryKey
holding arbitrary bytes to a StringKey
. You can use is_string
or is_binary
to find out whether the cast is safe.
let mut key = StringKey::new("user:/test/language")?;
// Cast the StringKey to BinaryKey
let binary_key = BinaryKey::from(key);
// And cast it back
let string_key = StringKey::from(binary_key);
With the KDB
struct you can access the key database.
See the full example for more. Run it from the example
directory using cargo run --bin kdb
.
The KDB error types are nested, so you can match on a high-level or a specific one. You might want to match all validation errors using kdb_error.is_validation()
which would include both syntactic and semantic validation errors.
For an in-depth explanation of the error types, see the error guideline.
extern crate elektra;
use elektra::{KeySet, StringKey, WriteableKey, KDB};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let contract = KeySet::with_capacity(0);
// Open a KDB session
let mut kdb = KDB::open(contract)?;
// Create a keyset that will hold the keys we get from the get call
let mut ks = KeySet::with_capacity(10);
// Get the current state of the key database
let mut parent_key = StringKey::new("user:/test")?;
let get_res = kdb.get(&mut ks, &mut parent_key);
if let Err(kdb_error) = get_res {
if kdb_error.is_validation() {
// Handle the validation error, which could be syntactic or semantic
// You could use is_semantic() or is_syntactic() to match further.
Ok(())
} else {
// Otherwise propagate the error up
Err(Box::new(kdb_error))
}
} else {
Ok(())
}
}
Safe wrappers are provided in the elektra
crate, however you can also use the raw bindings from elektra_sys
directly. Rust for instance does not allow the definition of variadic functions, but allows calling them. So you can call keyNew
as you would in C.
extern crate elektra_sys;
use elektra_sys::{keyDel, keyName, keyNew, keyString, KEY_END, KEY_VALUE};
use std::ffi::{CStr, CString};
fn main() {
let key_name = CString::new("user:/test/key").unwrap();
let key_val = CString::new("rust-bindings").unwrap();
let key = unsafe { keyNew(key_name.as_ptr(), KEY_VALUE, key_val.as_ptr(), KEY_END) };
let name_str = unsafe { CStr::from_ptr(keyName(key)) };
let val_str = unsafe { CStr::from_ptr(keyString(key)) };
println!("Key with name {:?} has value {:?}", name_str, val_str);
assert_eq!(unsafe { keyDel(key) }, 0);
}
Is automatically built on docs.rs
for elektra and elektra-sys. Note that since elektra-sys
is a one-to-one translation of the C API, it doesn't have documentation and you should instead use the C docs directly.
Documentation can also be built in the src/bindings/rust/
subdirectory of the build directory, by running cargo doc
and opening target/doc/elektra/index.html
.
Bindings are generated when building the elektra-sys
crate using rust-bindgen
. The build.rs
script in the elektra-sys
crate calls and configures bindgen. It also emits additional configuration for rustc
to tell it what library to link against, and where to find it.
Bindgen expects a wrapper.h
file that includes all headers that bindings should be generated for. Finally, bindgen outputs the bindings into a file, that is then included in the elektra-sys/lib.rs
file, where it can be used from other crates.
Rust-bindgen needs clang to generate the bindings, so if you encounter the following error, make sure clang (3.9 or higher) is installed.
/usr/include/limits.h:123:16: fatal error: 'limits.h' file not found
/usr/include/limits.h:123:16: fatal error: 'limits.h' file not found, err: true
thread 'main' panicked at 'Unable to generate bindings: ()', src/libcore/result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.