pipe-audit-core 0.2.1

Universal data validation and compliance audit engine
Documentation
use crate::logging::init::init_logging;
use crate::logging::ledger::ensure_ledger_key_exists;
use std::error::Error;
use std::fs;
use std::path::Path;

// Import necessary crates for CSV generation and fake data
use csv::Writer;
use fake::{Dummy, Fake, Faker};
use fake::faker::internet::en::SafeEmail;
use fake::faker::name::en::{FirstName, LastName};
use serde::Serialize;

/// Represents a single customer record for our sample CSV.
/// The `Serialize` trait allows this struct to be written to CSV format automatically.
/// The `Fake` trait allows us to generate random instances of this struct.
#[derive(Serialize, Dummy)]
struct Customer {
    id: i64,
    // 3. The attribute is now `dummy` and it takes a string faker.
    #[dummy(faker = "FirstName()")]
    first_name: String,
    #[dummy(faker = "LastName()")]
    last_name: String,
    #[dummy(faker = "SafeEmail()")]
    email: String,
}

/// Creates a sample customers.csv file with 30 rows of fake data.
fn create_sample_csv(path: &Path) -> Result<(), Box<dyn Error>> {
    let mut wtr = Writer::from_path(path)?;

    for i in 1..=30 {
        // The generation logic itself remains the same
        let mut customer: Customer = Faker.fake();
        customer.id = i;
        
        wtr.serialize(customer)?;
    }

    wtr.flush()?;
    Ok(())
}

pub fn init_project() -> Result<String, Box<dyn Error>> {
    init_logging();
    ensure_ledger_key_exists();

    let mut actions = Vec::new();

    // --- Create Directories ---
    let contracts_dir = Path::new("contracts");
    fs::create_dir_all(contracts_dir)?;

    let data_dir = Path::new("data");
    if !data_dir.exists() {
        fs::create_dir_all(data_dir)?;
        actions.push("Created data directory");
    }

    if !Path::new(".env").exists() {
        let env_content = r#"# .env file for project-specific environment variables

S3_ACCESS_KEY=<access_key>
S3_SECRET_KEY=<secret_key>

#Currently this uses the key from the storage account. If you use in production remember to rotate the key based on your companies policy
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=<account_name>;AccountKey=<account_key>;EndpointSuffix=core.windows.net

GCP_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"}'
"#;
        fs::write(".env", env_content)?;
        actions.push("Created .env file");
    }

    if !Path::new("profiles.toml").exists() {
        let profile_content = r#"# Pipe Audit profiles.toml example
[s3_test]
provider   = "s3"                       
endpoint   = "http://developyr.local:9000"    
region     = "us-east-1"                
access_key = "${S3_ACCESS_KEY}"     
secret_key = "${S3_SECRET_KEY}"
path_style = true
use_ssl = false

[azure_test]
provider = "azure"
connection_string = "${AZURE_STORAGE_CONNECTION_STRING}"

[gcs_test]
provider = "gcs"
service_account_json = "${GCP_SERVICE_ACCOUNT_KEY}"
"#;
        fs::write("profiles.toml", profile_content)?;
        actions.push("Created profiles.toml");
    }

    // Use `join` to build paths robustly for any OS.
    let contract_path = contracts_dir.join("example.toml");
    if !contract_path.exists() {
        let contract_content = r#"# contracts/example.toml
[contract]
name = "customers"
version = "0.1.1"
tags = ["pii", "critical"]

[file]
validation = [
  { rule = "row_count", min = 0, max = 20}
]

[[columns]]
name = "id"
validation = [
  { rule = "not_null" },
  { rule = "unique" }
]

[[columns]]
name = "email
validation = [
  { rule = "pattern", pattern = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$" }
]

[source]
type = "local"
location = "data/customers.csv"
profile = "local"

"#;
        fs::write(&contract_path, contract_content)?;
        actions.push("Created contracts/example.toml");
    }

    let customers_csv_path = data_dir.join("customers.csv");
    if !customers_csv_path.exists() {
        create_sample_csv(&customers_csv_path)?;
        actions.push("Created sample data/customers.csv");
    }

    if actions.is_empty() {
        Ok("Project already initialized. No changes were made.".to_string())
    } else {
        Ok(format!(
            "Successfully initialized project. Actions: {}",
            actions.join(", ")
        ))
    }
}