use crate::{
FieldInfo, ParseFieldResult, ValidationInfo, ValidatorAttr, find_value_field, parse_field,
parse_struct_options,
};
use insta::assert_debug_snapshot;
use quote::ToTokens;
#[allow(dead_code)]
#[derive(Debug)]
struct SnapshotValidator {
name: String,
infer_type: bool,
explicit_type: Option<String>,
args: Vec<(String, String)>,
}
#[allow(dead_code)]
#[derive(Debug)]
struct SnapshotValidationInfo {
field_validators: Vec<SnapshotValidator>,
element_validators: Vec<SnapshotValidator>,
is_nested: bool,
is_newtype: bool,
}
#[allow(dead_code)]
#[derive(Debug)]
struct SnapshotFieldInfo {
name: String,
member: String,
ty: String,
validation: SnapshotValidationInfo,
}
#[allow(dead_code)]
#[derive(Debug)]
enum SnapshotParseFieldResult {
Valid(Box<SnapshotFieldInfo>),
Skip,
Error(String),
}
fn normalize_tokens<T: ToTokens>(value: &T) -> String {
value.to_token_stream().to_string()
}
fn snapshot_validator(validator: &ValidatorAttr) -> SnapshotValidator {
SnapshotValidator {
name: validator.name().to_string(),
infer_type: validator.infer_type,
explicit_type: validator.explicit_type.as_ref().map(normalize_tokens),
args: validator
.args
.iter()
.map(|(name, expr)| (name.to_string(), normalize_tokens(expr)))
.collect(),
}
}
fn snapshot_validation(validation: &ValidationInfo) -> SnapshotValidationInfo {
SnapshotValidationInfo {
field_validators: validation
.field_validators
.iter()
.map(snapshot_validator)
.collect(),
element_validators: validation
.element_validators
.iter()
.map(snapshot_validator)
.collect(),
is_nested: validation.is_nested,
is_newtype: validation.is_newtype,
}
}
fn snapshot_field_info(info: FieldInfo) -> SnapshotFieldInfo {
SnapshotFieldInfo {
name: info.name.to_string(),
member: normalize_tokens(&info.member),
ty: normalize_tokens(&info.ty),
validation: snapshot_validation(&info.validation),
}
}
fn parse_field_result(field: &syn::Field) -> SnapshotParseFieldResult {
match parse_field(field, 0) {
ParseFieldResult::Valid(info) => {
SnapshotParseFieldResult::Valid(Box::new(snapshot_field_info(*info)))
},
ParseFieldResult::Skip => SnapshotParseFieldResult::Skip,
ParseFieldResult::Error(err) => SnapshotParseFieldResult::Error(err.to_string()),
}
}
fn parse_struct_options_result(item: &syn::ItemStruct) -> Result<(bool, bool), String> {
match parse_struct_options(&item.attrs) {
Ok(options) => Ok((options.try_new, options.newtype)),
Err(err) => Err(err.to_string()),
}
}
fn find_value_field_name(input: &syn::ItemStruct) -> Option<String> {
find_value_field(input).map(|(name, _)| name.to_string())
}
fn parse_field_snapshot(field: &syn::Field) -> Option<SnapshotFieldInfo> {
match parse_field(field, 0) {
ParseFieldResult::Valid(info) => Some(snapshot_field_info(*info)),
_ => None,
}
}
#[test]
fn test_parse_field_direct_single_validator() {
let field: syn::Field = syn::parse_quote! {
#[koruma(RangeValidation(min = 0, max = 100))]
pub age: i32
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_direct_multiple_validators() {
let field: syn::Field = syn::parse_quote! {
#[koruma(RangeValidation(min = 0, max = 100), EvenValidation)]
pub value: i32
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_direct_generic_validator() {
let field: syn::Field = syn::parse_quote! {
#[koruma(GenericRange::<_>(min = 0.0, max = 1.0))]
pub score: f64
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_direct_each() {
let field: syn::Field = syn::parse_quote! {
#[koruma(each(RangeValidation(min = 0, max = 100)))]
pub scores: Vec<i32>
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_direct_nested() {
let field: syn::Field = syn::parse_quote! {
#[koruma(nested)]
pub inner: InnerStruct
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_direct_newtype() {
let field: syn::Field = syn::parse_quote! {
#[koruma(newtype)]
pub index: CommonVariableIndex
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_direct_skip() {
let field: syn::Field = syn::parse_quote! {
#[koruma(skip)]
pub internal: u64
};
assert_debug_snapshot!(parse_field_result(&field));
}
#[test]
fn test_parse_field_cfg_attr_single_validator() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(RangeValidation(min = 0, max = 100)))]
pub age: i32
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_cfg_attr_multiple_validators() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(RangeValidation(min = 0, max = 100), EvenValidation))]
pub value: i32
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_cfg_attr_generic_validator() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(GenericRange::<_>(min = 0.0, max = 1.0)))]
pub score: f64
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_cfg_attr_each() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(each(RangeValidation(min = 0, max = 100))))]
pub scores: Vec<i32>
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_cfg_attr_nested() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(nested))]
pub inner: InnerStruct
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_cfg_attr_newtype() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(newtype))]
pub index: CommonVariableIndex
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_cfg_attr_skip() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(skip))]
pub internal: u64
};
assert_debug_snapshot!(parse_field_result(&field));
}
#[test]
fn test_parse_field_cfg_attr_with_other_derives() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "validation", derive(Clone), koruma(newtype))]
pub index: CommonVariableIndex
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_cfg_attr_koruma_first() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(RangeValidation(min = 0, max = 100)), some_other_attr)]
pub age: i32
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_cfg_attr_complex_condition() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(all(feature = "validation", not(test)), koruma(newtype))]
pub index: CommonVariableIndex
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_cfg_attr_any_condition() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(any(feature = "validation", feature = "full"), koruma(RangeValidation(min = 0, max = 100)))]
pub age: i32
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_struct_options_direct() {
let input: syn::ItemStruct = syn::parse_quote! {
#[koruma(try_new)]
pub struct Person {
pub age: i32,
}
};
assert_debug_snapshot!(parse_struct_options_result(&input));
}
#[test]
fn test_parse_struct_options_cfg_attr() {
let input: syn::ItemStruct = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(try_new))]
pub struct Person {
pub age: i32,
}
};
assert_debug_snapshot!(parse_struct_options_result(&input));
}
#[test]
fn test_parse_struct_options_cfg_attr_newtype() {
let input: syn::ItemStruct = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(newtype))]
pub struct Email(String);
};
assert_debug_snapshot!(parse_struct_options_result(&input));
}
#[test]
fn test_parse_struct_options_cfg_attr_both() {
let input: syn::ItemStruct = syn::parse_quote! {
#[cfg_attr(feature = "validation", koruma(try_new, newtype))]
pub struct Email(String);
};
assert_debug_snapshot!(parse_struct_options_result(&input));
}
#[test]
fn test_find_value_field_direct() {
let input: syn::ItemStruct = syn::parse_quote! {
pub struct Validator {
min: i32,
max: i32,
#[koruma(value)]
pub actual: Option<i32>,
}
};
assert_debug_snapshot!(find_value_field_name(&input));
}
#[test]
fn test_find_value_field_cfg_attr() {
let input: syn::ItemStruct = syn::parse_quote! {
pub struct Validator {
min: i32,
max: i32,
#[cfg_attr(feature = "validation", koruma(value))]
pub actual: Option<i32>,
}
};
assert_debug_snapshot!(find_value_field_name(&input));
}
#[test]
fn test_find_value_field_cfg_attr_complex_condition() {
let input: syn::ItemStruct = syn::parse_quote! {
pub struct Validator {
min: i32,
#[cfg_attr(all(feature = "validation", not(test)), koruma(value))]
pub actual: Option<i32>,
}
};
assert_debug_snapshot!(find_value_field_name(&input));
}
#[test]
fn test_parse_field_non_koruma_cfg_attr_skipped() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "serde", serde(rename = "something"))]
pub name: String
};
assert_debug_snapshot!(parse_field_result(&field));
}
#[test]
fn test_parse_field_mixed_attrs_only_koruma_parsed() {
let field: syn::Field = syn::parse_quote! {
#[cfg_attr(feature = "serde", serde(rename = "val"))]
#[cfg_attr(feature = "validation", koruma(RangeValidation(min = 0, max = 100)))]
#[doc = "Some documentation"]
pub value: i32
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}
#[test]
fn test_parse_field_unnamed() {
let field: syn::Field = syn::parse_quote! {
#[koruma(NonEmptyStringValidation)]
String
};
assert_debug_snapshot!(parse_field_snapshot(&field));
}