gpui-form-derive 0.7.0

Macro crate for gpui-form
Documentation
use darling::{Error as DarlingError, FromMeta};
use koruma_derive_core::{FieldInfo as KorumaFieldInfo, ParsedDataField};
use std::collections::HashMap;
use syn::DeriveInput;

use crate::derives::gpui_form::ir::{ValidationMetadata, ValidationRule};

#[derive(Clone, Debug)]
pub struct KorumaOptions {
    pub fluent: bool,
}

#[derive(Clone, Debug)]
pub struct KorumaField(pub KorumaOptions);

impl FromMeta for KorumaField {
    fn from_word() -> darling::Result<Self> {
        Ok(KorumaField(KorumaOptions { fluent: false }))
    }

    fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result<Self> {
        let [darling::ast::NestedMeta::Meta(syn::Meta::Path(path))] = items else {
            return Err(DarlingError::custom(
                "`koruma(...)` only accepts `fluent`, as in `#[gpui_form(koruma(fluent))]`",
            ));
        };

        if path.is_ident("fluent") {
            Ok(KorumaField(KorumaOptions { fluent: true }))
        } else {
            Err(DarlingError::custom(
                "`koruma(...)` only accepts `fluent`, as in `#[gpui_form(koruma(fluent))]`",
            )
            .with_span(path))
        }
    }
}

pub fn parse_koruma_fields(
    derive_input: &DeriveInput,
) -> syn::Result<HashMap<String, KorumaFieldInfo>> {
    let syn::Data::Struct(data_struct) = &derive_input.data else {
        return Ok(HashMap::new());
    };

    let mut fields = HashMap::new();
    let mut errors: Option<syn::Error> = None;
    for field in &data_struct.fields {
        let Some(ident) = field.ident.as_ref() else {
            continue;
        };
        match koruma_derive_core::parse_field(field, 0) {
            Ok(ParsedDataField::Participating(info)) => {
                fields.insert(ident.to_string(), info);
            },
            Ok(ParsedDataField::Unannotated(_)) | Ok(ParsedDataField::Skipped { .. }) => {},
            Err(error) => {
                if let Some(errors) = &mut errors {
                    errors.combine(error);
                } else {
                    errors = Some(error);
                }
            },
        }
    }

    if let Some(errors) = errors {
        Err(errors)
    } else {
        Ok(fields)
    }
}

pub fn validation_metadata_from_koruma(info: &KorumaFieldInfo) -> ValidationMetadata {
    let mut metadata = ValidationMetadata::default();
    for validator in info.field_validators() {
        let validator = validator.validator();
        if validator.name() == "RequiredValidation" {
            metadata.push(ValidationRule::Required);
        } else {
            metadata.push(ValidationRule::Validator(validator.name().to_string()));
        }
    }
    if info.is_newtype() {
        metadata.push(ValidationRule::Newtype);
    }
    if info.is_nested() {
        metadata.push(ValidationRule::Nested);
    }

    metadata
}