pray-core 1.5.1

Prayfile core: parse, resolve, lock, render, verify, and build distribution artifacts
Documentation
use crate::{PrayError, PrayResult};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RegistryTrustSettings {
    #[serde(default)]
    pub email_confirmation: EmailConfirmationPolicy,
    #[serde(default)]
    pub passkeys_enabled: bool,
    #[serde(default)]
    pub ssh_keys_enabled: bool,
    #[serde(default)]
    pub ssh_agent_signing_enabled: bool,
}

impl Default for RegistryTrustSettings {
    fn default() -> Self {
        Self {
            email_confirmation: EmailConfirmationPolicy::Required,
            passkeys_enabled: false,
            ssh_keys_enabled: false,
            ssh_agent_signing_enabled: false,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum EmailConfirmationPolicy {
    #[default]
    Required,
    Optional,
    Disabled,
}

impl RegistryTrustSettings {
    pub fn email_confirmation_label(&self) -> &'static str {
        match self.email_confirmation {
            EmailConfirmationPolicy::Required => "required",
            EmailConfirmationPolicy::Optional => "optional",
            EmailConfirmationPolicy::Disabled => "disabled",
        }
    }

    pub fn passkeys_label(&self) -> &'static str {
        if self.passkeys_enabled {
            "enabled"
        } else {
            "disabled"
        }
    }

    pub fn ssh_keys_label(&self) -> &'static str {
        if self.ssh_keys_enabled {
            "enabled"
        } else {
            "disabled"
        }
    }

    pub fn ssh_agent_label(&self) -> &'static str {
        if self.ssh_agent_signing_enabled {
            "enabled"
        } else {
            "disabled"
        }
    }
}

pub fn read_registry_trust_settings(root: &Path) -> PrayResult<RegistryTrustSettings> {
    let path = root.join("v1/trust.json");
    let Ok(text) = fs::read_to_string(&path) else {
        return Ok(RegistryTrustSettings::default());
    };
    let settings: RegistryTrustSettings =
        serde_json::from_str(&text).map_err(|error| PrayError::Parse {
            kind: "registry trust settings",
            message: error.to_string(),
        })?;
    Ok(settings)
}

pub fn write_registry_trust_settings(
    root: &Path,
    settings: &RegistryTrustSettings,
) -> PrayResult<()> {
    let path = root.join("v1/trust.json");
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(
        path,
        serde_json::to_string_pretty(settings)
            .map_err(|error| PrayError::Manifest(error.to_string()))?,
    )?;
    Ok(())
}

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

    #[test]
    fn defaults_require_email_confirmation_and_disable_other_methods() {
        let settings = RegistryTrustSettings::default();
        assert_eq!(settings.email_confirmation_label(), "required");
        assert_eq!(settings.passkeys_label(), "disabled");
        assert_eq!(settings.ssh_keys_label(), "disabled");
        assert_eq!(settings.ssh_agent_label(), "disabled");
    }

    #[test]
    fn parses_optional_email_confirmation_and_key_methods() {
        let settings: RegistryTrustSettings = serde_json::from_str(
            r#"{
                "email_confirmation": "optional",
                "passkeys_enabled": true,
                "ssh_keys_enabled": true,
                "ssh_agent_signing_enabled": true
            }"#,
        )
        .expect("parse trust settings");

        assert_eq!(
            settings.email_confirmation,
            EmailConfirmationPolicy::Optional
        );
        assert_eq!(settings.email_confirmation_label(), "optional");
        assert_eq!(settings.passkeys_label(), "enabled");
        assert_eq!(settings.ssh_keys_label(), "enabled");
        assert_eq!(settings.ssh_agent_label(), "enabled");
    }
}