Crates.io | uclicious |
lib.rs | uclicious |
version | 0.1.8 |
source | src |
created_at | 2020-03-15 22:50:49.724081 |
updated_at | 2023-04-25 19:08:26.724186 |
description | Uclicious is a wrapper around Universal Configuration Library (UCL) parser with a lot of sugar. |
homepage | https://github.com/andoriyu/uclicious |
repository | https://github.com/andoriyu/uclicious |
max_upload_size | |
id | 219026 |
size | 141,040 |
Uclicious is a flexible reduced boilerplate configuration framework.
Uclicious is built on top of libucl. If you ever wrote an nginx configurtion and though "Damn, I wish all configuration files were like this" this is the library for you. Internal parser supports both: nginx-like and json-like formats. JSON parser is a little bit more permissive than - every json file is a valid UCL file, but not other way around. It is much more complex than json or TOML, so I recommend reading documentaiton about it. Author of UCL did a great job documenting it. This library provides both: derive-driven and raw-api driven usage patterns.
Raw API involves interacting with libucl
parser via safe api:
use uclicious::*;
let mut parser = Parser::default();
let input = r#"
test_string = "no scope"
a_float = 3.14
an_integer = 69420
is_it_good = yes
buffer_size = 1KB
interval = 1s
"#;
parser.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let result = parser.get_object().unwrap();
let lookup_result = result.lookup("test_string").unwrap().as_string().unwrap();
assert_eq!(lookup_result.as_str(), "no scope");
let lookup_result = result.lookup("a_float").unwrap().as_f64().unwrap();
assert_eq!(lookup_result, 3.14f64);
let lookup_result = result.lookup("an_integer").unwrap().as_i64().unwrap();
assert_eq!(lookup_result, 69420i64);
let lookup_result = result.lookup("is_it_good").unwrap().as_bool().unwrap();
assert_eq!(lookup_result, true);
let lookup_result = result.lookup("buffer_size").unwrap().as_i64().unwrap();
assert_eq!(lookup_result, 1024);
let lookup_result = result.lookup("interval").unwrap().as_time().unwrap();
assert_eq!(lookup_result, 1.0f64);
In order to get around rust rules library implemets its own trait FromObject for some basic types:
use uclicious::*;
let mut parser = Parser::default();
let input = r#"
test_string = "no scope"
a_float = 3.14
an_integer = 69420
is_it_good = yes
buffer_size = 1KB
"#;
parser.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let result = parser.get_object().unwrap();
let lookup_result = result.lookup("is_it_good").unwrap();
let maybe: Option<bool> = FromObject::try_from(lookup_result).unwrap();
assert_eq!(Some(true), maybe);
On top of "raw" interface to libUCL, Uclicious provides an easy way to derive constructor for strucs:
use uclicious::*;
use std::path::PathBuf;
use std::net::SocketAddr;
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug,Uclicious)]
#[ucl(var(name = "test", value = "works"))]
struct Connection {
#[ucl(default)]
enabled: bool,
host: String,
#[ucl(default = "420")]
port: i64,
buffer: u64,
#[ucl(path = "type")]
kind: String,
locations: Vec<PathBuf>,
addr: SocketAddr,
extra: Extra,
#[ucl(path = "subsection.host")]
hosts: Vec<String>,
#[ucl(default)]
option: Option<String>,
gates: HashMap<String, bool>,
interval: Duration,
}
#[derive(Debug,Uclicious)]
#[ucl(skip_builder)]
struct Extra {
enabled: bool
}
let mut builder = Connection::builder().unwrap();
let input = r#"
enabled = yes
host = "some.fake.url"
buffer = 1mb
type = $test
locations = "/etc/"
addr = "127.0.0.1:80"
extra = {
enabled = on
}
subsection {
host = [host1, host2]
}
interval = 10ms
gates {
feature_1 = on
feature_2 = off
feature_3 = on
}"#;
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let connection: Connection = builder.build().unwrap();
If you choose to derive builder then ::builder()
method will be added to target struct.
Library supports running optional validators on values before building the resulting struct:
use uclicious::*;
mod validators {
use uclicious::ObjectError;
pub fn is_positive(lookup_path: &str, value: &i64) -> Result<(), ObjectError> {
if *value > 0 {
Ok(())
} else {
Err(ObjectError::other(format!("{} is not a positive number", lookup_path)))
}
}
}
#[derive(Debug,Uclicious)]
struct Validated {
#[ucl(default, validate="validators::is_positive")]
number: i64
}
let mut builder = Validated::builder().unwrap();
let input = "number = -1";
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
assert!(builder.build().is_err())
If your target structure has types that don't implement FromObject
you can use From
or TryFrom
via intermediate that does:
use uclicious::*;
use std::convert::{From,TryFrom};
#[derive(Debug, Eq, PartialEq)]
enum Mode {
On,
Off,
}
impl TryFrom<String> for Mode {
type Error = ObjectError;
fn try_from(src: String) -> Result<Mode, ObjectError> {
match src.to_lowercase().as_str() {
"on" => Ok(Mode::On),
"off" => Ok(Mode::Off),
_ => Err(ObjectError::other(format!("{} is not supported value", src)))
}
}
}
#[derive(Debug, Eq, PartialEq)]
struct WrappedInt(i64);
impl From<i64> for WrappedInt {
fn from(src: i64) -> WrappedInt {
WrappedInt(src)
}
}
#[derive(Debug,Uclicious, Eq, PartialEq)]
struct Mapped {
#[ucl(from="i64")]
number: WrappedInt,
#[ucl(try_from="String")]
mode: Mode
}
let mut builder = Mapped::builder().unwrap();
let input = r#"
number = -1,
mode = "on"
"#;
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let actual = builder.build().unwrap();
let expected = Mapped {
number: WrappedInt(-1),
mode: Mode::On
};
assert_eq!(expected, actual);
Additionally you can provide mapping to your type from ObjectRef:
use uclicious::*;
#[derive(Debug, Eq, PartialEq)]
pub enum Mode {
On,
Off,
}
pub fn map_bool(src: ObjectRef) -> Result<Mode, ObjectError> {
let bool: bool = src.try_into()?;
if bool {
Ok(Mode::On)
} else {
Ok(Mode::Off)
}
}
#[derive(Debug,Uclicious, Eq, PartialEq)]
struct Mapped {
#[ucl(map="map_bool")]
mode: Mode
}
let mut builder = Mapped::builder().unwrap();
let input = r#"
mode = on
"#;
builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
let actual = builder.build().unwrap();
let expected = Mapped {
mode: Mode::On
};
#[ucl(..)]
)skip_builder
parser(..)
flags
filevars(..)
set_filevars
on a parser.path
expand
pre_source_hook(...)
&mut Parser
as argument and return Result<(), Into<UclError>>
var(..)
name
$
part.value
include(..)
path
, chunk
or chunk_static
path = string
chunk = string
chunk_static = string
include_str!()
priority = u32
strategy = uclicious::DuplicateStrategy
All field level options are optional.
default
default = expression
path = string
validate = path::to_method
Fn(key: &str, value: &T) -> Result<(), E>
ObjectError
from = Type
ObjectRef
to Type
and then use std::convert::From
to convert into target typetry_from = Type
ObjectRef
to Type
and then use std::convert::TryFrom
to convert into target typeObjectError::Other
from_str
ObjectRef
to String
and then use std::str::FromStr
to convert into target typeObjectError::Other
map = path::to_method
Fn(src: ObjectRef) -> Result<T, E>
From
or TryFrom
or when error is not convertable into ObjectError
PRs, feature requests, bug reports are welcome. I won't be adding CoC — be civilized.
Optimize derive code.
Improve documentation — I often write late and night and some it might look like a word soup.
Better tests
Glob support in derive parser section
Variable handler
raw
module