verifyos-cli 0.2.1

A pure Rust CLI tool to scan Apple app bundles for App Store rejection risks before submission.
Documentation
use plist::Value;
use std::path::Path;

#[derive(Debug, thiserror::Error)]
pub enum PlistError {
    #[error("Plist Error: {0}")]
    ParseError(#[from] plist::Error),
    #[error("Not a dictionary")]
    NotADictionary,
}

pub struct InfoPlist {
    pub root: Value,
}

impl InfoPlist {
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, PlistError> {
        let value = Value::from_file(path)?;
        if value.as_dictionary().is_none() {
            return Err(PlistError::NotADictionary);
        }
        Ok(Self { root: value })
    }

    pub fn from_bytes(bytes: &[u8]) -> Result<Self, PlistError> {
        let value = Value::from_reader(std::io::Cursor::new(bytes))?;
        if value.as_dictionary().is_none() {
            return Err(PlistError::NotADictionary);
        }
        Ok(Self { root: value })
    }

    pub fn from_dictionary(dict: plist::Dictionary) -> Self {
        Self {
            root: Value::Dictionary(dict),
        }
    }

    pub fn get_string(&self, key: &str) -> Option<&str> {
        self.root
            .as_dictionary()
            .and_then(|dict| dict.get(key))
            .and_then(|v| v.as_string())
    }

    pub fn get_bool(&self, key: &str) -> Option<bool> {
        self.root
            .as_dictionary()
            .and_then(|dict| dict.get(key))
            .and_then(|v| v.as_boolean())
    }

    pub fn has_key(&self, key: &str) -> bool {
        self.root
            .as_dictionary()
            .map(|dict| dict.contains_key(key))
            .unwrap_or(false)
    }

    pub fn get_dictionary(&self, key: &str) -> Option<&plist::Dictionary> {
        self.root
            .as_dictionary()
            .and_then(|dict| dict.get(key))
            .and_then(|v| v.as_dictionary())
    }

    pub fn get_value(&self, key: &str) -> Option<&Value> {
        self.root.as_dictionary().and_then(|dict| dict.get(key))
    }

    pub fn get_array_strings(&self, key: &str) -> Option<Vec<String>> {
        self.root
            .as_dictionary()
            .and_then(|dict| dict.get(key))
            .and_then(|v| v.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|v| v.as_string().map(|s| s.to_string()))
                    .collect()
            })
    }
}