alef 0.67.3

Opinionated polyglot binding generator for Rust libraries
Documentation
use serde::{Deserialize, Serialize};

/// Indicates the core Rust type wraps the resolved type in a smart pointer or cow.
/// Used by codegen to generate correct From/Into conversions.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub enum CoreWrapper {
    #[default]
    None,
    /// `Cow<'static, str>` — binding uses String, core needs `.into()` ~keep
    Cow,
    /// `Arc<T>` — binding unwraps, core wraps with `Arc::new()` ~keep
    Arc,
    /// `bytes::Bytes` — binding uses `Vec<u8>`, core needs `Bytes::from()` ~keep
    Bytes,
    /// `Arc<Mutex<T>>` — binding wraps with `Arc::new(Mutex::new())`, methods call `.lock()` ~keep
    ArcMutex,
    /// `Box<str>` — binding uses String, core needs `.into()` (same shape as Cow
    /// but distinct so backends can keep wrapper-specific behavior addressable). ~keep
    Box,
}

/// Typed default value for a field, enabling backends to emit language-native defaults.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DefaultValue {
    BoolLiteral(bool),
    StringLiteral(String),
    IntLiteral(i64),
    FloatLiteral(f64),
    EnumVariant(String),
    /// A tuple-variant enum default (`Mode::Custom(5)`), each positional argument folded
    /// independently and kept in source order. Distinct from [`DefaultValue::EnumVariant`],
    /// which names a bare unit-variant path with no arguments of its own.
    ///
    /// Produced only when every argument itself folds to a value-carrying `DefaultValue` (see
    /// `extract::extractor::defaults::carries_value`); a call with even one unfoldable argument
    /// keeps the whole field [`DefaultValue::Unresolved`] rather than a partially-known payload
    /// — rendering some arguments as literals and silently dropping the rest would be a subtler
    /// instance of the fabrication `Unresolved` exists to prevent. ~keep
    TupleVariant(String, Vec<DefaultValue>),
    /// A struct-variant enum default (`Kind::Curated { label: "balanced".to_string() }`), each
    /// named field folded independently and kept in source order. Same all-or-nothing rule and
    /// rationale as [`DefaultValue::TupleVariant`]. ~keep
    StructVariant(String, Vec<(String, DefaultValue)>),
    /// A zero-argument Rust function that supplies the value at runtime. ~keep
    FunctionCall(String),
    /// A public zero-argument Rust function callable from generated binding crates. ~keep
    PublicFunctionCall(String),
    /// A non-empty collection literal, holding its elements in source order.
    ///
    /// A genuinely empty `vec![]`/`Vec::new()` stays [`DefaultValue::Empty`]: the two are
    /// distinct because every backend already renders "the empty collection" natively, whereas
    /// this variant carries elements that have to be rendered individually. Only produced when
    /// every element is itself representable — anything else falls back to `Empty`, so a
    /// backend never emits a default that silently differs from the Rust one. ~keep
    ListLiteral(Vec<DefaultValue>),
    /// Empty collection or `Default::default()` — the type's own zero, and known to be exactly
    /// what the Rust default is. Contrast [`DefaultValue::Unresolved`]. ~keep
    Empty,
    /// The extractor found the type's `Default` implementation but could not read a value out
    /// of it: the body is neither a struct literal nor a delegation alef can constant-fold
    /// (`Self::builder().build()`, a `match`, a computed constructor).
    ///
    /// Distinct from [`DefaultValue::Empty`], and the distinction is the entire point of the
    /// variant. `Empty` asserts *"the default is exactly this type's zero"* — true for
    /// `#[derive(Default)]`, for `Vec::new()`, for `Default::default()` — so a backend
    /// substituting its target language's zero is exact. `Unresolved` asserts the opposite:
    /// alef does **not** know the value, and a zero would be a guess.
    ///
    /// Before this variant existed both wrote `Empty`, so one enum value carried "exact" and
    /// "guess" at once and nothing could tell them apart. Every per-field-literal backend
    /// (C#, Java, Kotlin, Swift, Python, Go) then shipped its type-zero directly underneath a
    /// generated doc comment quoting the real Rust default — the value the extractor had
    /// already read out of the same doc prose.
    ///
    /// The payload is the source text of the `fn default()` body that could not be read, so a
    /// diagnostic can name it. ~keep
    Unresolved(String),
    /// None / null
    None,
}

/// Stable identity metadata for one error variant. ~keep
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct ErrorTaxonomy {
    #[serde(default)]
    pub code: u32,
    #[serde(default)]
    pub error_type: String,
    #[serde(default)]
    pub variant: String,
}

impl ErrorTaxonomy {
    pub fn for_variant(code: u32, error_type: &str, variant: &str) -> Self {
        Self {
            code,
            error_type: error_type.to_string(),
            variant: variant.to_string(),
        }
    }
}

/// Deprecation metadata extracted from `#[deprecated(...)]`.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct DeprecationInfo {
    /// Version when the item was deprecated (from `#[deprecated(since = "...")]`).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub since: Option<String>,
    /// Deprecation note (from `#[deprecated(note = "...")]`).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub note: Option<String>,
}

/// Version annotation on an IR item.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct VersionAnnotation {
    /// Version when this item was introduced (from `#[alef(since = "...")]`).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub since: Option<String>,
    /// Deprecation info (from `#[deprecated(...)]`).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub deprecated: Option<DeprecationInfo>,
}

#[cfg(test)]
mod error_taxonomy_tests {
    use super::ErrorTaxonomy;

    #[test]
    fn explicit_variant_code_is_preserved() {
        let taxonomy = ErrorTaxonomy::for_variant(101, "sample::RequestError", "InvalidInput");
        assert_eq!(taxonomy.code, 101);
        assert_eq!(taxonomy.error_type, "sample::RequestError");
        assert_eq!(taxonomy.variant, "InvalidInput");
    }

    #[test]
    fn legacy_serialized_taxonomy_defaults_compatibly() {
        let taxonomy: ErrorTaxonomy = serde_json::from_str("{}").expect("legacy metadata deserializes");

        assert_eq!(taxonomy, ErrorTaxonomy::default());
    }
}