rust-apk 1.0.0

Build, align, sign, verify, and inspect Android APKs in pure Rust
Documentation
use nutype::nutype;

/// An Android API level, as used by `minSdkVersion` and `targetSdkVersion`.
///
/// Android stores API levels as `int`, so valid values are `1..=i32::MAX`.
/// Named constants follow `android.os.Build.VERSION_CODES`.
#[nutype(
    const_fn,
    validate(greater_or_equal = 1, less_or_equal = 2_147_483_647),
    derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Display, TryFrom, Into, FromStr)
)]
pub struct ApiLevel(u32);

macro_rules! api_levels {
    ($($name:ident = $level:literal,)+) => {
        impl ApiLevel {
            $(pub const $name: Self = match Self::try_new($level) {
                Ok(level) => level,
                Err(_) => panic!("invalid API level constant"),
            };)+
        }
    };
}

api_levels! {
    BASE = 1,
    GINGERBREAD = 9,
    JELLY_BEAN_MR2 = 18,
    KITKAT = 19,
    LOLLIPOP = 21,
    M = 23,
    N = 24,
    O = 26,
    P = 28,
    Q = 29,
    R = 30,
    S = 31,
    TIRAMISU = 33,
    UPSIDE_DOWN_CAKE = 34,
    VANILLA_ICE_CREAM = 35,
    BAKLAVA = 36,
}

impl ApiLevel {
    pub const fn get(self) -> u32 {
        self.into_inner()
    }
}

/// An `android:versionCode` accepted by Google Play: `1..=2_100_000_000`.
#[nutype(
    const_fn,
    validate(greater_or_equal = 1, less_or_equal = 2_100_000_000),
    derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Display, TryFrom, Into, FromStr)
)]
pub struct VersionCode(u32);

impl VersionCode {
    pub const ONE: Self = match Self::try_new(1) {
        Ok(code) => code,
        Err(_) => panic!("invalid version code constant"),
    };

    pub const fn get(self) -> u32 {
        self.into_inner()
    }
}

impl Default for VersionCode {
    fn default() -> Self {
        Self::ONE
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn bounds() {
        assert!(matches!(ApiLevel::try_new(0), Err(ApiLevelError::GreaterOrEqualViolated)));
        assert!(matches!(ApiLevel::try_new(i32::MAX as u32 + 1), Err(ApiLevelError::LessOrEqualViolated)));
        assert_eq!(ApiLevel::try_new(24).unwrap(), ApiLevel::N);
        assert!(VersionCode::try_new(0).is_err());
        assert!(VersionCode::try_new(2_100_000_001).is_err());
        assert_eq!(VersionCode::try_new(2_100_000_000).unwrap().get(), 2_100_000_000);
    }
}