sails-idl-ast 1.0.0-beta.5

IDL AST model for the Sails framework
Documentation
//! Codec availability helpers for IDL annotations.
//!
//! Methods may carry a `@codec` annotation to restrict which dispatch paths
//! they participate in. The value is a comma-separated list of codecs
//! (e.g. `@codec: scale`, `@codec: ethabi`, `@codec: scale,ethabi`).
//! No codec annotations means both codecs (default).

use alloc::string::String;

type Annotation = (String, Option<String>);

/// Codec availability for a method's dispatch paths.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
struct Codecs {
    scale: bool,
    ethabi: bool,
}

impl Codecs {
    const BOTH: Self = Self {
        scale: true,
        ethabi: true,
    };
    const NONE: Self = Self {
        scale: false,
        ethabi: false,
    };
}

/// Resolves which codecs a method is available through based on its
/// `@codec` annotations.
///
/// Semantics:
/// - no `@codec` annotations → both codecs enabled (default)
/// - `@codec` with no value → both codecs enabled
/// - repeated `@codec` annotations → union of all declared codec values
/// - unknown codec tokens → ignored
/// - empty / whitespace-only tokens → ignored
fn codecs(annotations: &[Annotation]) -> Codecs {
    let mut result = Codecs::NONE;
    let mut saw_codec_annotation = false;

    for (name, value) in annotations {
        if name != "codec" {
            continue;
        }
        saw_codec_annotation = true;
        match value {
            None => {
                result = Codecs::BOTH;
            }
            Some(value) => {
                for token in value
                    .split(',')
                    .map(str::trim)
                    .filter(|token| !token.is_empty())
                {
                    match token {
                        "scale" => result.scale = true,
                        "ethabi" => result.ethabi = true,
                        _ => {}
                    }
                }
            }
        }
    }

    if !saw_codec_annotation {
        result = Codecs::BOTH;
    }

    result
}

/// Returns `true` if the method is available through SCALE/Gear dispatch.
pub fn has_scale_codec(annotations: &[Annotation]) -> bool {
    codecs(annotations).scale
}

/// Returns `true` if the method is available through Solidity ABI dispatch.
pub fn has_ethabi_codec(annotations: &[Annotation]) -> bool {
    codecs(annotations).ethabi
}

#[cfg(test)]
mod tests {
    use super::*;
    use alloc::string::ToString as _;
    use alloc::vec;
    use alloc::vec::Vec;

    fn ann(name: &str, value: Option<&str>) -> Annotation {
        (name.to_string(), value.map(|s| s.to_string()))
    }

    #[test]
    fn no_annotations_means_both() {
        let anns: Vec<Annotation> = vec![];
        assert!(has_scale_codec(&anns));
        assert!(has_ethabi_codec(&anns));
    }

    #[test]
    fn codec_scale_only() {
        let anns = vec![ann("codec", Some("scale"))];
        assert!(has_scale_codec(&anns));
        assert!(!has_ethabi_codec(&anns));
    }

    #[test]
    fn codec_ethabi_only() {
        let anns = vec![ann("codec", Some("ethabi"))];
        assert!(!has_scale_codec(&anns));
        assert!(has_ethabi_codec(&anns));
    }

    #[test]
    fn codec_both_explicit() {
        let anns = vec![ann("codec", Some("scale,ethabi"))];
        assert!(has_scale_codec(&anns));
        assert!(has_ethabi_codec(&anns));
    }

    #[test]
    fn codec_both_with_spaces() {
        let anns = vec![ann("codec", Some("scale, ethabi"))];
        assert!(has_scale_codec(&anns));
        assert!(has_ethabi_codec(&anns));
    }

    #[test]
    fn codec_no_value_means_both() {
        let anns = vec![ann("codec", None)];
        assert!(has_scale_codec(&anns));
        assert!(has_ethabi_codec(&anns));
    }

    #[test]
    fn repeated_codec_annotations_are_merged() {
        let anns = vec![ann("codec", Some("scale")), ann("codec", Some("ethabi"))];
        assert!(has_scale_codec(&anns));
        assert!(has_ethabi_codec(&anns));
    }

    #[test]
    fn unknown_and_empty_tokens_are_ignored() {
        let anns = vec![ann("codec", Some("scale, ,unknown"))];
        assert!(has_scale_codec(&anns));
        assert!(!has_ethabi_codec(&anns));
    }

    #[test]
    fn other_annotations_are_ignored_when_codec_missing() {
        let anns = vec![ann("query", None), ann("payable", None)];
        assert!(has_scale_codec(&anns));
        assert!(has_ethabi_codec(&anns));
    }
}