harn-cli 0.10.92

CLI for the Harn programming language — run, test, REPL, format, and lint
Documentation
use harn_parser::parse_source;

use super::external_action::literal_union;
use super::support::ProtocolArtifactSource;

pub(super) const ACTIVITY_VOCABULARY_SOURCE: &str =
    "crates/harn-stdlib/src/stdlib/activity/vocabulary.harn";

#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ActivityVocabulary {
    pub(super) kinds: Vec<String>,
    pub(super) permission_outcomes: Vec<String>,
    pub(super) permission_deciders: Vec<String>,
    pub(super) permission_policy_layers: Vec<String>,
    pub(super) permission_policy_outcomes: Vec<String>,
    pub(super) permission_grant_scopes: Vec<String>,
    pub(super) permission_grant_expiries: Vec<String>,
}

impl ActivityVocabulary {
    pub(super) fn load(source: &ProtocolArtifactSource) -> Result<Self, String> {
        let text = source.read_text(ACTIVITY_VOCABULARY_SOURCE)?;
        Self::parse(&text)
    }

    fn parse(source: &str) -> Result<Self, String> {
        let program = parse_source(source)
            .map_err(|error| format!("failed to parse {ACTIVITY_VOCABULARY_SOURCE}: {error}"))?;
        let union = |name| literal_union(&program, name, ACTIVITY_VOCABULARY_SOURCE);
        Ok(Self {
            kinds: union("ActivityKind")?,
            permission_outcomes: union("ToolPermissionOutcome")?,
            permission_deciders: union("ToolPermissionDecider")?,
            permission_policy_layers: union("ToolPermissionPolicyLayer")?,
            permission_policy_outcomes: union("ToolPermissionPolicyOutcome")?,
            permission_grant_scopes: union("ToolPermissionGrantScope")?,
            permission_grant_expiries: union("ToolPermissionGrantExpiry")?,
        })
    }

    #[cfg(test)]
    pub(super) fn load_for_tests() -> Self {
        let source =
            ProtocolArtifactSource::from_anchor(std::path::Path::new(env!("CARGO_MANIFEST_DIR")))
                .expect("harn-cli is compiled from the Harn workspace");
        Self::load(&source).expect("activity vocabulary is valid")
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use harn_vm::orchestration::{
        ActivityKind, ToolPermissionDecider, ToolPermissionGrantExpiry, ToolPermissionGrantScope,
        ToolPermissionOutcome, ToolPermissionPolicyLayer, ToolPermissionPolicyOutcome,
    };

    fn wire_values<T: serde::Serialize>(values: &[T]) -> Vec<String> {
        values
            .iter()
            .map(|value| {
                serde_json::to_value(value)
                    .unwrap()
                    .as_str()
                    .unwrap()
                    .to_string()
            })
            .collect()
    }

    #[test]
    fn parses_closed_activity_vocabulary() {
        let vocabulary = ActivityVocabulary::parse(
            r#"
pub type ActivityKind = "external_action" | "tool_permission"
pub type ToolPermissionOutcome = "approved"
pub type ToolPermissionDecider = "person"
pub type ToolPermissionPolicyLayer = "user_policy"
pub type ToolPermissionPolicyOutcome = "allowed"
pub type ToolPermissionGrantScope = "once"
pub type ToolPermissionGrantExpiry = "after_dispatch"
"#,
        )
        .unwrap();
        assert_eq!(vocabulary.kinds, ["external_action", "tool_permission"]);
        assert_eq!(vocabulary.permission_outcomes, ["approved"]);
        assert_eq!(vocabulary.permission_deciders, ["person"]);
    }

    #[test]
    fn runtime_projection_matches_harn_owned_vocabulary() {
        let vocabulary = ActivityVocabulary::load_for_tests();
        assert_eq!(vocabulary.kinds, wire_values(&ActivityKind::ALL));
        assert_eq!(
            vocabulary.permission_outcomes,
            wire_values(&ToolPermissionOutcome::ALL)
        );
        assert_eq!(
            vocabulary.permission_deciders,
            wire_values(&ToolPermissionDecider::ALL)
        );
        assert_eq!(
            vocabulary.permission_policy_layers,
            wire_values(&ToolPermissionPolicyLayer::ALL)
        );
        assert_eq!(
            vocabulary.permission_policy_outcomes,
            wire_values(&ToolPermissionPolicyOutcome::ALL)
        );
        assert_eq!(
            vocabulary.permission_grant_scopes,
            wire_values(&ToolPermissionGrantScope::ALL)
        );
        assert_eq!(
            vocabulary.permission_grant_expiries,
            wire_values(&ToolPermissionGrantExpiry::ALL)
        );
    }
}