Skip to content

Commit

Permalink
feat(derive): Fields mappers and validators
Browse files Browse the repository at this point in the history
Closes #3
  • Loading branch information
andoriyu authored Apr 4, 2020
2 parents a78b93a + 0a0790a commit e308cd4
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 95 deletions.
99 changes: 93 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,89 @@
//!
//! If you choose to derive builder then `::builder()` method will be added to target struct.
//!
//! #### Validators
//!
//! Library supports running optional validators on values before building the resulting struct:
//!
//! ```rust
//! 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())
//! ```
//! #### Type Mapping
//!
//! If your target structure has types that don't implement `FromObject` you can use `From` or `TryFrom`
//! via intermediate that does:
//!
//! ```rust
//! 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);
//! ```
//! ### Supported attributes (`#[ucl(..)]`)
//!
//! #### Structure level
Expand Down Expand Up @@ -150,24 +233,28 @@
//! - Used to add files into the parser.
//! - If file doesn't exist or failed to parse, then error will be returned in a constructor.
//! - Has following nested attirbutes:
//! - (required) `path(string)`
//! - (required) `path = string`
//! - File path. Can be absolute or relative to CWD.
//! - (optional) `priority(u32)`
//! - (optional) `priority = u32`
//! - 0-15 priority for the source. Consult the libUCL documentation for more information.
//! - (optional) `strategy(uclicious::DuplicateStrategy)`
//! - (optional) `strategy = uclicious::DuplicateStrategy`
//! - Strategy to use for duplicate keys. Consult the libUCL documentation for more information.
//!
//! #### Field level
//! All field level options are optional.
//!
//! - `default`
//! - Use Default::default if key not found in object.
//! - `default(expression)`
//! - `default = expression`
//! - Use this _expression_ as value if key not found.
//! - Could be a value or a function call.
//! - `path(string)`
//! - `path = string`
//! - By default field name is used as path.
//! - If set that would be used as a key.
//! - dot notation for key is supported.
//! - `validate = path::to_method`
//! - `Fn(key: &str, value: &T) -> Result<(), E>`
//! - Error needs to be convertable into `ObjectError`
//!
//! ### Additional notes
//! - If target type is an array, but key is a single value — an implicit list is created.
Expand All @@ -183,7 +270,7 @@
//! PRs, feature requests, bug reports are welcome. I won't be adding CoC — be civilized.
//!
//! #### Particular Contributions of Interest
//!
//!
//! - Optimize derive code.
//! - Improve documentation — I often write late and night and some it might look like a word soup.
//! - Better tests
Expand Down
2 changes: 0 additions & 2 deletions src/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ mod test {
use crate::raw::object::Object;
use crate::raw::parser::Parser;
use libucl_bind::ucl_type_t;
use std::error::Error;
use std::path::Display;

#[test]
fn string_parsing() {
Expand Down
10 changes: 5 additions & 5 deletions src/raw/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::raw::{utils, Priority};
use crate::traits::FromObject;
use bitflags::_core::borrow::Borrow;
use bitflags::_core::convert::Infallible;
use bitflags::_core::fmt::{Formatter, Display};
use bitflags::_core::fmt::{Display, Formatter};
use libucl_bind::{
ucl_object_frombool, ucl_object_fromdouble, ucl_object_fromint, ucl_object_fromstring,
ucl_object_get_priority, ucl_object_key, ucl_object_lookup, ucl_object_lookup_path,
Expand All @@ -18,12 +18,12 @@ use std::convert::TryInto;
use std::error::Error;
use std::ffi::CStr;
use std::fmt;
use std::hash::BuildHasher;
use std::mem::MaybeUninit;
use std::net::{AddrParseError, SocketAddr};
use std::num::TryFromIntError;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::hash::BuildHasher;
use std::time::Duration;

/// Errors that could be returned by `Object` or `ObjectRef` functions.
Expand Down Expand Up @@ -62,7 +62,7 @@ impl ObjectError {
}

/// Create a new error `Other` by extracting the error description.
pub fn other<E: Error + Display>(err: E) -> ObjectError {
pub fn other<E: Display>(err: E) -> ObjectError {
ObjectError::Other(err.to_string())
}
}
Expand Down Expand Up @@ -616,10 +616,10 @@ where
}
}

impl<T,S> FromObject<ObjectRef> for HashMap<String, T, S>
impl<T, S> FromObject<ObjectRef> for HashMap<String, T, S>
where
T: FromObject<ObjectRef> + Clone,
S: BuildHasher + Default
S: BuildHasher + Default,
{
fn try_from(value: ObjectRef) -> Result<Self, ObjectError> {
if ucl_type_t::UCL_OBJECT != value.kind {
Expand Down
8 changes: 4 additions & 4 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ pub trait FromObject<T>: Sized {
fn try_from(value: T) -> Result<Self, ObjectError>;
}

pub trait TryInto<T> :Sized {
pub trait TryInto<T>: Sized {
fn try_into(self) -> Result<T, ObjectError>;
}

impl<T, U> TryInto<U> for T
where
U: FromObject<T>,
where
U: FromObject<T>,
{
fn try_into(self) -> Result<U, ObjectError> {
U::try_from(self)
}
}
}
80 changes: 71 additions & 9 deletions uclicious-example/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,51 @@
use uclicious::*;
use std::path::PathBuf;
use std::net::SocketAddr;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::net::SocketAddr;
use std::path::PathBuf;
use uclicious::*;

#[derive(Debug, Eq, PartialEq)]
pub struct WrappedInt(pub i64);

impl From<i64> for WrappedInt {
fn from(src: i64) -> Self {
WrappedInt(src)
}
}

#[derive(Debug, Eq, PartialEq)]
pub enum Visibility {
Hidden,
Visible,
}

impl TryFrom<String> for Visibility {
type Error = ObjectError;

#[derive(Debug,Uclicious)]
fn try_from(src: String) -> Result<Self, Self::Error> {
match src.to_lowercase().as_str() {
"hidden" => Ok(Visibility::Hidden),
"visible" => Ok(Visibility::Visible),
_ => Err(ObjectError::other(format!(
"{} is not supported visibility type",
src
))),
}
}
}
#[derive(Debug, Uclicious)]
#[ucl(var(name = "test", value = "works"))]
#[ucl(include(path = "test.ucl"))]
#[ucl(include(path = "another-test.ucl", strategy = "DuplicateStrategy::UCL_DUPLICATE_MERGE", priority = 10))]
#[ucl(include(
path = "another-test.ucl",
strategy = "DuplicateStrategy::UCL_DUPLICATE_MERGE",
priority = 10
))]
pub struct Connection {
#[ucl(default)]
enabled: bool,
host: String,
#[ucl(default = "420")]
#[ucl(default = "420", validate = "validators::is_positive")]
port: i64,
buffer: u64,
#[ucl(path = "type")]
Expand All @@ -24,20 +58,44 @@ pub struct Connection {
#[ucl(default)]
option: Option<String>,
gates: HashMap<String, bool>,
#[ucl(from = "i64")]
wrapped_int: WrappedInt,
#[ucl(try_from = "String", validate = "validators::is_okay")]
visibility: Visibility,
}

#[derive(Debug, Uclicious)]
#[ucl(skip_builder)]
pub struct Extra {
enabled: bool
enabled: bool,
}
mod validators {
use crate::Visibility;
use uclicious::ObjectError;

#[inline(always)]
pub fn is_positive(path: &str, val: &i64) -> Result<(), ObjectError> {
if *val > 0 {
Ok(())
} else {
Err(ObjectError::other(format!(
"{} is not a positive number",
path
)))
}
}

pub fn is_okay(_path: &str, _val: &Visibility) -> Result<(), ObjectError> {
Ok(())
}
}
fn main() {
let mut builder = Connection::builder().unwrap();

let input = r#"
enabled = yes
host = "some.fake.url"
port = 20
buffer = 1mb
type = $test
locations = "/etc/"
Expand All @@ -53,9 +111,13 @@ fn main() {
feature_2 = off
feature_3 = on
}
wrapped_int = 1
visibility = "hidden"
"#;

builder.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY).unwrap();
builder
.add_chunk_full(input, Priority::default(), DEFAULT_DUPLICATE_STRATEGY)
.unwrap();
let connection: Connection = builder.build().unwrap();
dbg!(connection);
}
}
5 changes: 2 additions & 3 deletions uclicious_derive/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(unused)]

use syn::{Path, Type};
use proc_macro2::Ident;
use syn::{Path, Type};

pub fn string_ty() -> Path {
syn::parse_str("::std::string::String").unwrap()
Expand Down Expand Up @@ -105,7 +105,6 @@ pub fn path_ty() -> Path {
syn::parse_str("::std::path::Path").unwrap()
}


pub fn ucl_default_strategy() -> Path {
syn::parse_str("::uclicious::DEFAULT_DUPLICATE_STRATEGY").unwrap()
}
}
Loading

0 comments on commit e308cd4

Please sign in to comment.