pipa_core/engine/
init.rs

1use crate::logging::init::init_logging;
2use crate::logging::ledger::ensure_ledger_key_exists;
3use std::error::Error;
4use std::fs;
5use std::path::Path;
6
7// Import necessary crates for CSV generation and fake data
8use csv::Writer;
9use fake::{Dummy, Fake, Faker};
10use fake::faker::internet::en::SafeEmail;
11use fake::faker::name::en::{FirstName, LastName};
12use serde::Serialize;
13
14/// Represents a single customer record for our sample CSV.
15/// The `Serialize` trait allows this struct to be written to CSV format automatically.
16/// The `Fake` trait allows us to generate random instances of this struct.
17#[derive(Serialize, Dummy)]
18struct Customer {
19    id: i64,
20    // 3. The attribute is now `dummy` and it takes a string faker.
21    #[dummy(faker = "FirstName()")]
22    first_name: String,
23    #[dummy(faker = "LastName()")]
24    last_name: String,
25    #[dummy(faker = "SafeEmail()")]
26    email: String,
27}
28
29/// Creates a sample customers.csv file with 30 rows of fake data.
30fn create_sample_csv(path: &Path) -> Result<(), Box<dyn Error>> {
31    let mut wtr = Writer::from_path(path)?;
32
33    for i in 1..=30 {
34        // The generation logic itself remains the same
35        let mut customer: Customer = Faker.fake();
36        customer.id = i;
37        
38        wtr.serialize(customer)?;
39    }
40
41    wtr.flush()?;
42    Ok(())
43}
44
45pub fn init_project() -> Result<String, Box<dyn Error>> {
46    init_logging();
47    ensure_ledger_key_exists();
48
49    let mut actions = Vec::new();
50
51    // --- Create Directories ---
52    let contracts_dir = Path::new("contracts");
53    fs::create_dir_all(contracts_dir)?;
54
55    let data_dir = Path::new("data");
56    if !data_dir.exists() {
57        fs::create_dir_all(data_dir)?;
58        actions.push("Created data directory");
59    }
60
61    if !Path::new(".env").exists() {
62        let env_content = r#"# .env file for project-specific environment variables
63
64S3_ACCESS_KEY=<access_key>
65S3_SECRET_KEY=<secret_key>
66
67#Currently this uses the key from the storage account. If you use in production remember to rotate the key based on your companies policy
68AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=<account_name>;AccountKey=<account_key>;EndpointSuffix=core.windows.net
69
70GCP_SERVICE_ACCOUNT_KEY='{"type": "service_account","project_id": <your_project_id>,"private_key_id": <your_private_key_id>,"private_key": "-----BEGIN PRIVATE KEY-----<The actual key from the file, this will be very long>-----END PRIVATE KEY-----\n","client_email": <user@project.iam.gserviceaccount.com>,"client_id": <your_client_id>,"auth_uri": "https://accounts.google.com/o/oauth2/auth","token_uri": "https://oauth2.googleapis.com/token","auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/<email>","universe_domain": "googleapis.com"}'
71"#;
72        fs::write(".env", env_content)?;
73        actions.push("Created .env file");
74    }
75
76    if !Path::new("profiles.toml").exists() {
77        let profile_content = r#"# Pipe Audit profiles.toml example
78[s3_test]
79provider   = "s3"                       
80endpoint   = "http://developyr.local:9000"    
81region     = "us-east-1"                
82access_key = "${S3_ACCESS_KEY}"     
83secret_key = "${S3_SECRET_KEY}"
84path_style = true
85use_ssl = false
86
87[azure_test]
88provider = "azure"
89connection_string = "${AZURE_STORAGE_CONNECTION_STRING}"
90
91[gcs_test]
92provider = "gcs"
93service_account_json = "${GCP_SERVICE_ACCOUNT_KEY}"
94"#;
95        fs::write("profiles.toml", profile_content)?;
96        actions.push("Created profiles.toml");
97    }
98
99    // Use `join` to build paths robustly for any OS.
100    let contract_path = contracts_dir.join("example.toml");
101    if !contract_path.exists() {
102        let contract_content = r#"# contracts/example.toml
103[contract]
104name = "customers"
105version = "0.1.1"
106tags = ["pii", "critical"]
107
108[file]
109validation = [
110  { rule = "row_count", min = 0, max = 20}
111]
112
113[[columns]]
114name = "id"
115validation = [
116  { rule = "not_null" },
117  { rule = "unique" }
118]
119
120[[columns]]
121name = "email
122validation = [
123  { rule = "pattern", pattern = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$" }
124]
125
126[source]
127type = "local"
128location = "data/customers.csv"
129profile = "local"
130
131"#;
132        fs::write(&contract_path, contract_content)?;
133        actions.push("Created contracts/example.toml");
134    }
135
136    let customers_csv_path = data_dir.join("customers.csv");
137    if !customers_csv_path.exists() {
138        create_sample_csv(&customers_csv_path)?;
139        actions.push("Created sample data/customers.csv");
140    }
141
142    if actions.is_empty() {
143        Ok("Project already initialized. No changes were made.".to_string())
144    } else {
145        Ok(format!(
146            "Successfully initialized project. Actions: {}",
147            actions.join(", ")
148        ))
149    }
150}