mirror of
https://github.com/kristoferssolo/filecaster.git
synced 2025-10-21 19:00:34 +00:00
test: add integration tests
This commit is contained in:
parent
360f75cb1a
commit
cd8fd27e69
12
README.md
12
README.md
@ -15,23 +15,21 @@ Procedural macro to derive configuration from files, with optional merging capab
|
||||
filecaster = "0.1"
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```rust
|
||||
use filecaster::FromFile;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, FromFile)]
|
||||
#[derive(Debug, Clone, FromFile)]
|
||||
pub struct MyConfig {
|
||||
#[from_file(default = "localhost")]
|
||||
pub host: String,
|
||||
#[default = "8080"]
|
||||
#[from_file(default = 8080)]
|
||||
pub port: u16,
|
||||
#[default = "false"]
|
||||
#[from_file(default = false)]
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Simulate loading from a file (e.g., JSON, YAML)
|
||||
// Simulate loading from a file (e.g., JSON, YAML, TOML)
|
||||
let file_content = r#"
|
||||
{
|
||||
"host": "localhost"
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
Attribute, Data, DeriveInput, Error, Expr, Fields, FieldsNamed, GenericParam, Generics, Meta,
|
||||
Result, WhereClause, WherePredicate, parse_quote, parse2,
|
||||
Attribute, Data, DeriveInput, Error, Expr, Fields, FieldsNamed, GenericParam, Generics, Lit,
|
||||
Meta, MetaList, Result, WhereClause, WherePredicate, parse_quote, parse2,
|
||||
};
|
||||
|
||||
const WITH_MERGE: bool = cfg!(feature = "merge");
|
||||
@ -155,18 +155,7 @@ fn parse_from_file_default_attr(attrs: &[Attribute]) -> Result<Option<Expr>> {
|
||||
|
||||
// Parse the content inside the parentheses of #[from_file(...)]
|
||||
return match &attr.meta {
|
||||
Meta::List(meta_list) => {
|
||||
let mut default_expr = None;
|
||||
meta_list.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("default") {
|
||||
let value = meta.value()?;
|
||||
let expr = value.parse::<Expr>()?;
|
||||
default_expr = Some(expr);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(default_expr)
|
||||
}
|
||||
Meta::List(meta_list) => parse_default(meta_list),
|
||||
_ => Err(Error::new_spanned(
|
||||
attr,
|
||||
"Expected #[from_file(default = \"literal\")] or similar",
|
||||
@ -176,6 +165,28 @@ fn parse_from_file_default_attr(attrs: &[Attribute]) -> Result<Option<Expr>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn parse_default(list: &MetaList) -> Result<Option<Expr>> {
|
||||
let mut default_expr = None;
|
||||
list.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("default") {
|
||||
let value = meta.value()?;
|
||||
let expr = value.parse::<Expr>()?;
|
||||
|
||||
if let Expr::Lit(expr_lit) = &expr {
|
||||
if let Lit::Str(lit_str) = &expr_lit.lit {
|
||||
default_expr = Some(parse_quote! {
|
||||
#lit_str.to_string()
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
default_expr = Some(expr);
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(default_expr)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use claims::{assert_err, assert_none};
|
||||
@ -213,18 +224,6 @@ mod tests {
|
||||
assert_err!(extract_named_fields(&input));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_default_attrs_picks_first_default() {
|
||||
let attrs: Vec<Attribute> = vec![
|
||||
parse_quote!(#[foo]),
|
||||
parse_quote!(#[from_file(default = "bar")]),
|
||||
parse_quote!(#[from_file(default = "baz")]),
|
||||
];
|
||||
let expr = parse_from_file_default_attr(&attrs).unwrap().unwrap();
|
||||
// should pick the first default attribute
|
||||
assert_eq!(expr, parse_quote!("bar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_default_attrs_none() {
|
||||
let attrs: Vec<Attribute> = vec![parse_quote!(#[foo])];
|
||||
@ -303,10 +302,15 @@ mod tests {
|
||||
fn build_derive_clause_defaults() {
|
||||
let derive_ts = build_derive_clause();
|
||||
let s = derive_ts.to_string();
|
||||
dbg!(&s);
|
||||
assert!(s.contains(
|
||||
"derive (Debug , Clone , Default , serde :: Deserialize , serde :: Serialize)"
|
||||
));
|
||||
if WITH_MERGE {
|
||||
assert!(s.contains(
|
||||
"derive (Debug , Clone , Default , serde :: Deserialize , serde :: Serialize , merge :: Merge)"
|
||||
));
|
||||
} else {
|
||||
assert!(s.contains(
|
||||
"derive (Debug , Clone , Default , serde :: Deserialize , serde :: Serialize)"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
111
tests/from_file.rs
Normal file
111
tests/from_file.rs
Normal file
@ -0,0 +1,111 @@
|
||||
use filecaster::FromFile;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, FromFile)]
|
||||
struct Simple {
|
||||
x: i32,
|
||||
#[from_file(default = "hello")]
|
||||
y: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, FromFile)]
|
||||
struct NumericDefault {
|
||||
a: i32,
|
||||
#[from_file(default = 42)]
|
||||
b: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_defaults() {
|
||||
// No file passed -> all fields fall back to defaults
|
||||
let s = Simple::from_file(None);
|
||||
assert_eq!(
|
||||
s,
|
||||
Simple {
|
||||
x: 0,
|
||||
y: "hello".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_override() {
|
||||
// Manually construct the generated `SimpleFile` and override both fields
|
||||
let file = SimpleFile {
|
||||
x: Some(10),
|
||||
y: Some("world".to_string()),
|
||||
};
|
||||
let s = Simple::from_file(Some(file));
|
||||
assert_eq!(
|
||||
s,
|
||||
Simple {
|
||||
x: 10,
|
||||
y: "world".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_serde_empty() {
|
||||
// Deserialize from JSON missing both fields -> both None
|
||||
let json = "{}";
|
||||
let file: SimpleFile = serde_json::from_str(json).unwrap();
|
||||
let s = Simple::from_file(Some(file));
|
||||
assert_eq!(s.x, 0);
|
||||
assert_eq!(s.y, "hello".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_serde_partial() {
|
||||
// Deserialize from JSON with only `x`
|
||||
let json = r#"{"x":5}"#;
|
||||
let file: SimpleFile = serde_json::from_str(json).unwrap();
|
||||
let s = Simple::from_file(Some(file));
|
||||
assert_eq!(s.x, 5);
|
||||
assert_eq!(s.y, "hello".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_serde_full() {
|
||||
// Deserialize from JSON with both fields
|
||||
let json = r#"{"x":7,"y":"rust"}"#;
|
||||
let file: SimpleFile = serde_json::from_str(json).unwrap();
|
||||
let s = Simple::from_file(Some(file));
|
||||
assert_eq!(s.x, 7);
|
||||
assert_eq!(s.y, "rust".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_numeric_default() {
|
||||
// No file -> default `b = 42`
|
||||
let n = NumericDefault::from_file(None);
|
||||
assert_eq!(n, NumericDefault { a: 0, b: 42 });
|
||||
|
||||
// Override both
|
||||
let file = NumericDefaultFile {
|
||||
a: Some(7),
|
||||
b: Some(99),
|
||||
};
|
||||
let n2 = NumericDefault::from_file(Some(file));
|
||||
assert_eq!(n2, NumericDefault { a: 7, b: 99 });
|
||||
}
|
||||
|
||||
#[cfg(feature = "merge")]
|
||||
mod merge_tests {
|
||||
use super::*;
|
||||
use merge::Merge;
|
||||
|
||||
#[test]
|
||||
fn test_merge_simple_file() {
|
||||
let mut f1 = SimpleFile {
|
||||
x: Some(1),
|
||||
y: None,
|
||||
};
|
||||
let f2 = SimpleFile {
|
||||
x: None,
|
||||
y: Some("foo".to_string()),
|
||||
};
|
||||
f1.merge(f2);
|
||||
assert_eq!(f1.x, Some(1));
|
||||
assert_eq!(f1.y, Some("foo".to_string()));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user