gpui-form-derive 0.7.0

Macro crate for gpui-form
Documentation
use super::support::*;

#[test]
fn test_koruma_field_parsing_with_cfg_attr() {
    let tokens = quote! {
        struct Test {
            #[cfg_attr(feature = "validation", koruma(SomeValidator::<_>))]
            field: u32,
        }
    };
    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let result = parse_field_after_cfg_attr_flattening(derive_input, 0);

    match result {
        Ok(ParsedDataField::Participating(info)) => {
            assert!(
                !info.field_validators().is_empty(),
                "Should find validators after cfg_attr flattening"
            );
            assert_eq!(
                info.field_validators()[0].validator().name().to_string(),
                "SomeValidator",
                "Should extract correct validator name"
            );
        },
        Ok(ParsedDataField::Unannotated(_)) | Ok(ParsedDataField::Skipped { .. }) => {
            panic!("parse_field returned Skip after gpui-form cfg_attr flattening");
        },
        Err(e) => {
            panic!("parse_field returned Error: {}", e);
        },
    }
}

#[test]
fn test_koruma_field_parsing_newtype_in_cfg_attr() {
    let tokens = quote! {
        struct Test {
            #[cfg_attr(feature = "validation", koruma(newtype))]
            field: SomeNewtype,
        }
    };
    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let result = parse_field_after_cfg_attr_flattening(derive_input, 0);

    match result {
        Ok(ParsedDataField::Participating(info)) => {
            assert!(
                info.is_newtype(),
                "Should detect newtype after cfg_attr flattening"
            );
        },
        Ok(ParsedDataField::Unannotated(_)) | Ok(ParsedDataField::Skipped { .. }) => {
            panic!("parse_field returned Skip after gpui-form cfg_attr flattening for newtype");
        },
        Err(e) => {
            panic!("parse_field returned Error: {}", e);
        },
    }
}

#[test]
fn test_koruma_field_parsing_nested_in_cfg_attr() {
    let tokens = quote! {
        struct Test {
            #[cfg_attr(feature = "validation", koruma(nested))]
            field: NestedStruct,
        }
    };
    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let result = parse_field_after_cfg_attr_flattening(derive_input, 0);

    match result {
        Ok(ParsedDataField::Participating(info)) => {
            assert!(
                info.is_nested(),
                "Should detect nested after cfg_attr flattening"
            );
        },
        Ok(ParsedDataField::Unannotated(_)) | Ok(ParsedDataField::Skipped { .. }) => {
            panic!("parse_field returned Skip after gpui-form cfg_attr flattening for nested");
        },
        Err(e) => {
            panic!("parse_field returned Error: {}", e);
        },
    }
}

#[test]
fn test_cfg_attr_end_to_end_validation_generation() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        #[gpui_form(koruma(fluent))]
        struct TestForm {
            #[gpui_form(component(crate::Input))]
            #[cfg_attr(feature = "validation", koruma(koruma_collection::collection::NonEmptyValidation::<_>))]
            name: String,

            #[gpui_form(component(crate::NumericShape))]
            #[cfg_attr(feature = "validation", koruma(koruma_collection::numeric::PositiveValidation::<_>))]
            age: u32,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let flattened_derive_input = flatten_cfg_attr_in_derive_input(derive_input.clone());

    if let syn::Data::Struct(data_struct) = &flattened_derive_input.data {
        for (idx, field) in data_struct.fields.iter().enumerate() {
            let result = koruma_derive_core::parse_field(field, idx);
            match result {
                Ok(ParsedDataField::Participating(info)) => {
                    assert!(
                        !info.field_validators().is_empty(),
                        "Field {} should have validators detected from cfg_attr",
                        idx
                    );
                    let validator_names: Vec<String> = info
                        .field_validators()
                        .iter()
                        .map(|v| v.validator().name().to_string())
                        .collect();
                    assert!(
                        !validator_names.is_empty(),
                        "Field {} should have named validators",
                        idx
                    );
                },
                Ok(ParsedDataField::Unannotated(_)) | Ok(ParsedDataField::Skipped { .. }) => {
                    panic!("Field {} should have validators, got Skip", idx);
                },
                Err(e) => {
                    panic!("Field {} parsing failed: {}", idx, e);
                },
            }
        }
    }

    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let expanded_str = expanded.to_string();

    assert!(
        expanded_str.contains("Koruma") || expanded_str.contains("koruma"),
        "Generated code should include Koruma-related types"
    );

    assert!(
        expanded_str.contains("NonEmptyValidation") && expanded_str.contains("PositiveValidation"),
        "Generated code should preserve parsed validation chains: {}",
        &expanded_str[..expanded_str.len().min(500)]
    );

    assert!(
        expanded_str.contains("FieldVariant") || expanded_str.contains("validations"),
        "Generated code should include field variant metadata"
    );
}

#[test]
fn test_real_world_common_v_read_pattern() {
    let tokens = quote! {
        #[cfg_attr(feature = "ui", derive(GpuiForm))]
        #[cfg_attr(feature = "ui", gpui_form(koruma(fluent)))]
        pub struct CommonVRead {
            #[cfg_attr(feature = "ui", gpui_form(component(crate::NumericShape)))]
            #[cfg_attr(feature = "validation", koruma(newtype))]
            pub index: CommonVariableIndex,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let result = parse_field_after_cfg_attr_flattening(derive_input.clone(), 0);

    match result {
        Ok(ParsedDataField::Participating(info)) => {
            assert!(
                info.is_newtype(),
                "Should detect newtype validation after cfg_attr flattening"
            );
        },
        Ok(ParsedDataField::Unannotated(_)) | Ok(ParsedDataField::Skipped { .. }) => {
            panic!(
                "koruma_derive_core returned Skip for field with koruma(newtype) after gpui-form cfg_attr flattening"
            );
        },
        Err(e) => {
            panic!("koruma_derive_core returned Error: {}", e);
        },
    }

    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let expanded_str = expanded.to_string();

    assert!(
        expanded_str.contains("with_validations"),
        "Generated GpuiFormShape should call with_validations()"
    );
}

#[test]
fn test_koruma_enabled_without_validators_derives_validate() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        #[gpui_form(koruma(fluent))]
        struct OptionalOnlyForm {
            #[gpui_form(hidden)]
            note: Option<String>,
            #[gpui_form(hidden)]
            kind: Option<u8>,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let expanded_str = compact_tokens(&expanded.to_string());

    assert!(
        expanded_str.contains("::koruma::Koruma"),
        "Koruma derive should be emitted when gpui_form(koruma) is enabled, even without validators"
    );
    assert!(
        expanded_str.contains("::koruma::KorumaAllFluent"),
        "KorumaAllFluent derive should be emitted when gpui_form(koruma(fluent)) is enabled"
    );
}

#[test]
fn test_validator_attr_to_tokens_normalizes_direct_chain() {
    let validator: ValidatorAttr = syn::parse_quote!(
        koruma_collection::numeric::RangeValidation::<_>
            .min(18)
            .max(167)
    );

    let tokens = koruma::validator_attr_to_tokens(&validator);
    let compact = compact_tokens(&tokens.to_string());

    assert_eq!(
        compact,
        compact_tokens("koruma_collection::numeric::RangeValidation::<_>.min(18).max(167)")
    );
}

#[test]
fn test_gpui_form_preserves_direct_chain_koruma_validators() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        #[gpui_form(koruma)]
        struct TestForm {
            #[gpui_form(component(crate::NumericShape))]
            #[koruma(koruma_collection::numeric::RangeValidation::<_>.min(18).max(167))]
            age: u32,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let compact = compact_tokens(&expanded.to_string());

    assert!(
        compact.contains(&compact_tokens(
            "koruma_collection::numeric::RangeValidation::<_>.min(18).max(167)"
        )),
        "Generated value holder should preserve direct-chain koruma validators: {compact}"
    );
}

#[test]
fn test_gpui_form_preserves_zero_setter_koruma_direct_chains() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        #[gpui_form(koruma)]
        struct TestForm {
            #[gpui_form(component(crate::NumericShape))]
            #[koruma(koruma_collection::numeric::PositiveValidation::<_>)]
            age: u32,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let compact = compact_tokens(&expanded.to_string());

    assert!(
        compact.contains(&compact_tokens(
            "koruma_collection::numeric::PositiveValidation::<_>"
        )),
        "Generated value holder should preserve zero-setter koruma direct chains: {compact}"
    );
}

#[test]
fn test_gpui_form_inherits_required_policy_without_field_override() {
    let tokens = quote! {
        #[derive(GpuiForm)]
        #[gpui_form(koruma)]
        struct TestForm {
            #[gpui_form(component(crate::Input))]
            name: String,
        }
    };

    let derive_input: DeriveInput = syn::parse2(tokens).unwrap();
    let expanded = expansion::expand_gpui_form(
        derive_input,
        structs::GpuiFormOptions {
            generate_shape: true,
            generate_mcp: false,
        },
    );

    let compact = compact_tokens(&expanded.to_string());

    assert!(
        compact.contains(
            "if<<crate::Inputas::gpui_form::runtime::shape::GpuiFormComponentShapePolicy>::ValueStoragePolicyas::gpui_form::runtime::shape::ComponentValueStoragePolicy>::REQUIRES_VALUE{::gpui_form::schema::registry::FieldValuePresence::RequiresValue}else{::gpui_form::schema::registry::FieldValuePresence::DirectStorage}"
        ),
        "FieldVariant should inherit value-storage policy from the shape: {compact}"
    );
    assert!(
        compact.contains("__TestFormFormValueHolderValueStoragePolicyValidation")
            && compact.contains("ValueStorage<Value>>::is_present(value)")
            && compact.contains("&[::gpui_form::schema::registry::ValidationRuleId::Required,]"),
        "shape-inherited requiredness should use the policy-aware validator and conditional metadata: {compact}"
    );
    assert!(
        compact.contains("impl::core::convert::TryFrom<TestFormFormValueHolder>forTestForm"),
        "Fallible holders should keep the standard TryFrom impl: {compact}"
    );
    assert!(
        compact.contains("with_holder_conversion(::gpui_form::schema::registry::HolderConversionMetadata::new(::gpui_form::schema::registry::HolderConversionShape::FallibleRequired")
            && compact.contains(
                "false||<<crate::Inputas::gpui_form::runtime::shape::GpuiFormComponentShapePolicy>::ValueStoragePolicyas::gpui_form::runtime::shape::ComponentValueStoragePolicy>::REQUIRES_VALUE"
            ),
        "Inventory metadata should record the fallible API shape and runtime policy predicate: {compact}"
    );
    assert!(
        compact.contains(
            "pubfntry_into_original(self)->Result<TestForm,TestFormFormValueHolderConversionError>"
        ),
        "Fallible holders should expose try_into_original(self): {compact}"
    );
    assert!(
        !compact.contains("pubfntry_from("),
        "Generated holders should expose TryFrom through the standard trait only"
    );
}