pipa_core/engine/contracts/
meta.rs

1//! Contract metadata and syntax validation functions
2
3use crate::contracts::load_contract_for_file; // loads and parses TOML into SchemaContracts
4use crate::engine::log_action; // audit logging hook
5use glob; // filesystem globbing
6use std::path::Path;
7
8/// Result of listing contracts
9pub struct ContractList {
10    pub contracts: Vec<String>, // contract names (file stems)
11}
12
13/// Result of getting a contract
14pub struct ContractInfo {
15    pub name: String,    // contract name
16    pub version: String, // version string from contract metadata
17    pub exists: bool,    // whether the contract file exists
18}
19
20/// Result of validating a contract
21pub struct ContractValidation {
22    pub valid: bool,           // true if contract parsed successfully
23    pub error: Option<String>, // error message if invalid
24}
25
26/// List all available contracts by scanning `contracts/*.toml`.
27/// Returns both the list and a log message.
28pub fn list_contracts() -> Result<(ContractList, String), String> {
29    let contracts: Vec<String> = match glob::glob("contracts/*.toml") {
30        Ok(paths) => paths
31            .filter_map(Result::ok)
32            .filter_map(|p| {
33                p.file_stem()
34                    .and_then(|s| s.to_str())
35                    .map(|s| s.to_string())
36            })
37            .collect(),
38        Err(_) => return Err("Failed to read contracts directory".to_string()),
39    };
40
41    let message = log_action("contracts_listed", None, None, None, None);
42    Ok((ContractList { contracts }, message))
43}
44
45/// Get information about a specific contract.
46/// Returns metadata (name, version, exists) and a log message.
47pub fn get_contract(name: &str) -> (ContractInfo, String) {
48    let contract_path = format!("contracts/{}.toml", name);
49
50    if !Path::new(&contract_path).exists() {
51        let message = log_action(
52            "contract_retrieved",
53            Some("exists=false"),
54            Some(name),
55            None,
56            None,
57        );
58        return (
59            ContractInfo {
60                name: name.to_string(),
61                version: "".to_string(),
62                exists: false,
63            },
64            message,
65        );
66    }
67
68    let contract = load_contract_for_file(Path::new(&contract_path));
69    let message = log_action(
70        "contract_retrieved",
71        Some("exists=true"),
72        Some(&contract.contract.name),
73        Some(&contract.contract.version),
74        None,
75    );
76    (
77        ContractInfo {
78            name: contract.contract.name,
79            version: contract.contract.version,
80            exists: true,
81        },
82        message,
83    )
84}
85
86/// Validate a contract's syntax and structure.
87/// Attempts to parse the TOML file into a SchemaContracts.
88/// Returns validation result and a log message.
89pub fn validate_contract(name: &str) -> (ContractValidation, String) {
90    let contract_path = format!("contracts/{}.toml", name);
91
92    if !Path::new(&contract_path).exists() {
93        let message = log_action(
94            "contract_validated",
95            Some("error=Contract not found"),
96            Some(name),
97            None,
98            None,
99        );
100        return (
101            ContractValidation {
102                valid: false,
103                error: Some("Contract not found".to_string()),
104            },
105            message,
106        );
107    }
108
109    match std::panic::catch_unwind(|| load_contract_for_file(Path::new(&contract_path))) {
110        Ok(contract) => {
111            let message = log_action(
112                "contract_validated",
113                Some("valid=true"),
114                Some(&contract.contract.name),
115                Some(&contract.contract.version),
116                None,
117            );
118            (
119                ContractValidation {
120                    valid: true,
121                    error: None,
122                },
123                message,
124            )
125        }
126        Err(_) => {
127            let message = log_action(
128                "contract_validated",
129                Some("error=Contract failed to parse"),
130                Some(name),
131                None,
132                None,
133            );
134            (
135                ContractValidation {
136                    valid: false,
137                    error: Some("Contract failed to parse".to_string()),
138                },
139                message,
140            )
141        }
142    }
143}