pipecircuit-cli 0.0.11

Command-line architectural tools for Pipecircuit programs
use std::{
    collections::BTreeMap,
    path::{Path, PathBuf},
    rc::Rc,
};

use serde::{Deserialize, Serialize};

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(super) enum Role {
    Core,
    Pipes,
    Switchboard,
    Wiring,
    Application,
}

impl std::fmt::Display for Role {
    fn fmt(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        output.write_str(match self {
            Self::Core => "core",
            Self::Pipes => "pipes",
            Self::Switchboard => "switchboard",
            Self::Wiring => "wiring",
            Self::Application => "application",
        })
    }
}

impl Role {
    pub(super) fn pure(self) -> bool {
        matches!(self, Self::Core | Self::Pipes | Self::Switchboard)
    }
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub(super) struct Declaration {
    pub role: Role,
    pub app: Option<String>,
    pub scope: Option<Scope>,
    #[serde(default)]
    pub forbid_dependencies: Vec<String>,
}

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub(super) enum Scope {
    Shared,
}

impl Declaration {
    pub(super) fn valid(&self) -> bool {
        (match self.role {
            Role::Core => self.app.is_none() && self.scope.is_none(),
            Role::Pipes | Role::Wiring => matches!(
                (&self.app, self.scope),
                (Some(_), None) | (None, Some(Scope::Shared))
            ),
            Role::Switchboard | Role::Application => self.app.is_some() && self.scope.is_none(),
        }) && self
            .app
            .as_ref()
            .is_none_or(|app| !app.is_empty() && app != "shared")
    }

    pub(super) fn allows(&self, target: &Self) -> bool {
        match self.role {
            Role::Core => target.role == Role::Core,
            Role::Pipes | Role::Switchboard => {
                target.role == Role::Core
                    || (target.role == Role::Pipes
                        && (target.scope == Some(Scope::Shared)
                            || (self.app.is_some() && self.app == target.app)))
            }
            Role::Wiring | Role::Application => true,
        }
    }
}

#[derive(Debug, Serialize)]
pub(super) struct Package {
    pub name: String,
    pub manifest: PathBuf,
    pub declaration: Option<Declaration>,
    pub provenance: &'static str,
    pub dependencies: Vec<Dependency>,
    #[serde(skip)]
    pub document: Rc<toml::Value>,
    #[serde(skip)]
    pub library: Option<PathBuf>,
    #[serde(skip)]
    pub workspace_lints: Rc<toml::Value>,
}

#[derive(Debug, Serialize)]
pub(super) struct Dependency {
    pub name: String,
    pub alias: Option<String>,
    pub path: Option<PathBuf>,
    pub kind: String,
    pub target: Option<String>,
    pub optional: bool,
    pub default_features: bool,
    pub features: Vec<String>,
}

#[derive(Debug, Serialize)]
pub(super) struct Finding {
    pub code: &'static str,
    pub manifest: PathBuf,
    pub subject: String,
    pub explanation: String,
    pub guidance: &'static str,
}

#[derive(Default, Debug, Serialize)]
pub(super) struct WorkCounts {
    pub metadata_invocations: usize,
    pub manifests_read: usize,
    pub sources_read: usize,
    pub dependency_edges_checked: usize,
}

#[derive(Debug, Serialize)]
pub(super) struct Architecture {
    pub schema: &'static str,
    pub root: PathBuf,
    pub packages: BTreeMap<PathBuf, Package>,
    pub findings: Vec<Finding>,
    pub limitations: Vec<&'static str>,
    pub work: WorkCounts,
}

impl Architecture {
    pub(super) fn report(
        &mut self,
        package: &Path,
        code: &'static str,
        subject: String,
        explanation: String,
        guidance: &'static str,
    ) {
        self.findings.push(Finding {
            code,
            manifest: package.to_owned(),
            subject,
            explanation,
            guidance,
        });
    }
}