use crate::module::traits::{ModuleError, ModuleMetadata};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MaintainerSignature {
pub name: String,
pub public_key: String,
pub signature: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignatureSection {
#[serde(default)]
pub maintainers: Vec<MaintainerSignature>,
#[serde(default)]
pub threshold: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BinarySection {
#[serde(default)]
pub hash: Option<String>,
#[serde(default)]
pub size: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PaymentSection {
#[serde(default)]
pub required: bool,
#[serde(default)]
pub price_sats: Option<u64>,
#[serde(default)]
pub author_payment_code: Option<String>,
#[serde(default)]
pub author_address: Option<String>,
#[serde(default)]
pub commons_payment_code: Option<String>,
#[serde(default)]
pub commons_address: Option<String>,
#[serde(default)]
pub payment_signature: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleManifest {
pub name: String,
pub version: String,
pub entry_point: String,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub author: Option<String>,
#[serde(default)]
pub capabilities: Vec<String>,
#[serde(default)]
pub dependencies: HashMap<String, String>,
#[serde(default)]
pub optional_dependencies: HashMap<String, String>,
#[serde(default)]
pub config_schema: HashMap<String, String>,
#[serde(default)]
pub signatures: Option<SignatureSection>,
#[serde(default)]
pub binary: Option<BinarySection>,
#[serde(default)]
pub payment: Option<PaymentSection>,
}
impl ModuleManifest {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, ModuleError> {
let contents = std::fs::read_to_string(path.as_ref()).map_err(|e| {
ModuleError::InvalidManifest(format!("Failed to read manifest file: {e}"))
})?;
let manifest: ModuleManifest = toml::from_str(&contents).map_err(|e| {
ModuleError::InvalidManifest(format!("Failed to parse manifest TOML: {e}"))
})?;
if manifest.name.is_empty() {
return Err(ModuleError::InvalidManifest(
"Module name cannot be empty".to_string(),
));
}
if manifest.entry_point.is_empty() {
return Err(ModuleError::InvalidManifest(
"Entry point cannot be empty".to_string(),
));
}
Ok(manifest)
}
pub fn to_metadata(&self) -> ModuleMetadata {
ModuleMetadata {
name: self.name.clone(),
version: self.version.clone(),
description: self.description.clone().unwrap_or_default(),
author: self.author.clone().unwrap_or_default(),
capabilities: self.capabilities.clone(),
dependencies: self.dependencies.clone(),
optional_dependencies: self.optional_dependencies.clone(),
entry_point: self.entry_point.clone(),
}
}
pub fn get_threshold(&self) -> Option<(usize, usize)> {
let threshold_str = self.signatures.as_ref()?.threshold.as_ref()?;
Self::parse_threshold(threshold_str)
}
pub fn parse_threshold(threshold_str: &str) -> Option<(usize, usize)> {
let parts: Vec<&str> = threshold_str.split("-of-").collect();
if parts.len() != 2 {
return None;
}
let required = parts[0].parse().ok()?;
let total = parts[1].parse().ok()?;
if required > total || required == 0 {
return None;
}
Some((required, total))
}
pub fn get_signatures(&self) -> Vec<(String, String)> {
self.signatures
.as_ref()
.map(|s| {
s.maintainers
.iter()
.map(|m| (m.name.clone(), m.signature.clone()))
.collect()
})
.unwrap_or_default()
}
pub fn get_public_keys(&self) -> HashMap<String, String> {
self.signatures
.as_ref()
.map(|s| {
s.maintainers
.iter()
.map(|m| (m.name.clone(), m.public_key.clone()))
.collect()
})
.unwrap_or_default()
}
pub fn has_signatures(&self) -> bool {
self.signatures.is_some()
&& self
.signatures
.as_ref()
.map(|s| !s.maintainers.is_empty())
.unwrap_or(false)
}
}
impl TryFrom<ModuleManifest> for ModuleMetadata {
type Error = ModuleError;
fn try_from(manifest: ModuleManifest) -> Result<Self, Self::Error> {
Ok(manifest.to_metadata())
}
}