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