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"
|
filecaster = "0.1"
|
||||||
```
|
```
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use filecaster::FromFile;
|
use filecaster::FromFile;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, FromFile)]
|
#[derive(Debug, Clone, FromFile)]
|
||||||
pub struct MyConfig {
|
pub struct MyConfig {
|
||||||
|
#[from_file(default = "localhost")]
|
||||||
pub host: String,
|
pub host: String,
|
||||||
#[default = "8080"]
|
#[from_file(default = 8080)]
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
#[default = "false"]
|
#[from_file(default = false)]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
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#"
|
let file_content = r#"
|
||||||
{
|
{
|
||||||
"host": "localhost"
|
"host": "localhost"
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{
|
use syn::{
|
||||||
Attribute, Data, DeriveInput, Error, Expr, Fields, FieldsNamed, GenericParam, Generics, Meta,
|
Attribute, Data, DeriveInput, Error, Expr, Fields, FieldsNamed, GenericParam, Generics, Lit,
|
||||||
Result, WhereClause, WherePredicate, parse_quote, parse2,
|
Meta, MetaList, Result, WhereClause, WherePredicate, parse_quote, parse2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const WITH_MERGE: bool = cfg!(feature = "merge");
|
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(...)]
|
// Parse the content inside the parentheses of #[from_file(...)]
|
||||||
return match &attr.meta {
|
return match &attr.meta {
|
||||||
Meta::List(meta_list) => {
|
Meta::List(meta_list) => parse_default(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)
|
|
||||||
}
|
|
||||||
_ => Err(Error::new_spanned(
|
_ => Err(Error::new_spanned(
|
||||||
attr,
|
attr,
|
||||||
"Expected #[from_file(default = \"literal\")] or similar",
|
"Expected #[from_file(default = \"literal\")] or similar",
|
||||||
@ -176,6 +165,28 @@ fn parse_from_file_default_attr(attrs: &[Attribute]) -> Result<Option<Expr>> {
|
|||||||
Ok(None)
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use claims::{assert_err, assert_none};
|
use claims::{assert_err, assert_none};
|
||||||
@ -213,18 +224,6 @@ mod tests {
|
|||||||
assert_err!(extract_named_fields(&input));
|
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]
|
#[test]
|
||||||
fn parse_default_attrs_none() {
|
fn parse_default_attrs_none() {
|
||||||
let attrs: Vec<Attribute> = vec![parse_quote!(#[foo])];
|
let attrs: Vec<Attribute> = vec![parse_quote!(#[foo])];
|
||||||
@ -303,11 +302,16 @@ mod tests {
|
|||||||
fn build_derive_clause_defaults() {
|
fn build_derive_clause_defaults() {
|
||||||
let derive_ts = build_derive_clause();
|
let derive_ts = build_derive_clause();
|
||||||
let s = derive_ts.to_string();
|
let s = derive_ts.to_string();
|
||||||
dbg!(&s);
|
if WITH_MERGE {
|
||||||
|
assert!(s.contains(
|
||||||
|
"derive (Debug , Clone , Default , serde :: Deserialize , serde :: Serialize , merge :: Merge)"
|
||||||
|
));
|
||||||
|
} else {
|
||||||
assert!(s.contains(
|
assert!(s.contains(
|
||||||
"derive (Debug , Clone , Default , serde :: Deserialize , serde :: Serialize)"
|
"derive (Debug , Clone , Default , serde :: Deserialize , serde :: Serialize)"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_trait_bouds_appends_default() {
|
fn add_trait_bouds_appends_default() {
|
||||||
|
|||||||
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