use crate::utils::error::{Error, Result};
use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub mod adapter_generator;
pub mod part_compiler;
pub mod part_signer;
pub use adapter_generator::AdapterGenerator;
pub use part_compiler::PartCompiler;
pub use part_signer::PartSigner;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PartSpec {
pub id: String,
pub version: String,
pub part_type: String,
pub rdf_source: String,
pub target_language: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PartManifest {
pub interfaces: Vec<InterfaceSpec>,
pub dependencies: HashMap<String, String>,
pub config: serde_json::Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InterfaceSpec {
pub name: String,
pub params: serde_json::Value,
pub returns: serde_json::Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ManufacturedPart {
pub id: String,
pub version: String,
pub payload: Vec<u8>,
pub payload_hash: String,
pub payload_size: u64,
pub interfaces: Vec<ExportedInterface>,
pub compiler_output: String,
pub adapter_source: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExportedInterface {
pub name: String,
pub params: serde_json::Value,
pub returns: serde_json::Value,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SignedPart {
pub manufactured: ManufacturedPart,
pub signature: String,
pub verifying_key: String,
pub trust_tier: String,
}
pub struct PartFoundry {
compiler: PartCompiler,
signer: PartSigner,
generator: AdapterGenerator,
}
impl PartFoundry {
pub fn new(compiler: PartCompiler, signer: PartSigner, generator: AdapterGenerator) -> Self {
Self {
compiler,
signer,
generator,
}
}
pub async fn manufacture(
&self, spec: PartSpec, manifest: PartManifest, output_dir: &Path,
) -> Result<SignedPart> {
if spec.id.is_empty() {
return Err(Error::new("Part ID cannot be empty"));
}
if spec.version.is_empty() {
return Err(Error::new("Part version cannot be empty"));
}
if spec.rdf_source.is_empty() {
return Err(Error::new("RDF source cannot be empty"));
}
let adapter_source = self
.generator
.generate(&spec, &manifest)
.await
.map_err(|e| Error::new(&format!("Adapter generation failed: {}", e)))?;
let compiled = self
.compiler
.compile(&spec.part_type, &adapter_source)
.await
.map_err(|e| Error::new(&format!("Compilation failed: {}", e)))?;
let payload_hash = calculate_blake3(&compiled);
let interfaces: Vec<ExportedInterface> = manifest
.interfaces
.into_iter()
.map(|i| ExportedInterface {
name: i.name,
params: i.params,
returns: i.returns,
})
.collect();
let manufactured = ManufacturedPart {
id: spec.id.clone(),
version: spec.version.clone(),
payload_size: compiled.len() as u64,
payload_hash: payload_hash.clone(),
payload: compiled,
interfaces,
compiler_output: String::new(),
adapter_source,
};
let signed = self
.signer
.sign_part(manufactured)
.map_err(|e| Error::new(&format!("Signing failed: {}", e)))?;
let part_dir = output_dir.join(&spec.id).join(&spec.version);
std::fs::create_dir_all(&part_dir)
.map_err(|e| Error::new(&format!("Failed to create part dir: {}", e)))?;
let manifest_path = part_dir.join("part.json");
let manifest_json = serde_json::to_string_pretty(&signed)
.map_err(|e| Error::new(&format!("Failed to serialize manifest: {}", e)))?;
std::fs::write(&manifest_path, manifest_json)
.map_err(|e| Error::new(&format!("Failed to write manifest: {}", e)))?;
let binary_path = part_dir.join(format!("{}.bin", spec.id));
std::fs::write(&binary_path, &signed.manufactured.payload)
.map_err(|e| Error::new(&format!("Failed to write binary: {}", e)))?;
Ok(signed)
}
}
fn calculate_blake3(data: &[u8]) -> String {
use blake3::Hasher;
let mut hasher = Hasher::new();
hasher.update(data);
hasher.finalize().to_hex().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part_spec_validation() {
let spec = PartSpec {
id: "test-part".to_string(),
version: "1.0.0".to_string(),
part_type: "wasm32".to_string(),
rdf_source: "(RDF source)".to_string(),
target_language: "rust".to_string(),
};
assert!(!spec.id.is_empty());
assert!(!spec.version.is_empty());
}
#[test]
fn test_manufactured_part_fields() {
let part = ManufacturedPart {
id: "test".to_string(),
version: "1.0.0".to_string(),
payload: vec![1, 2, 3],
payload_hash: "abc123".to_string(),
payload_size: 3,
interfaces: vec![],
compiler_output: String::new(),
adapter_source: String::new(),
};
assert_eq!(part.payload_size, 3);
assert_eq!(part.payload.len() as u64, part.payload_size);
}
}