use crate::{AssetId, Key, Plan, ValidationError, validation::invalid};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WorkspaceAsset {
pub id: AssetId,
pub key: Key,
pub label: String,
pub kind: AssetKind,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum AssetKind {
GitRepository {
remotes: Vec<String>,
},
Folder,
DocumentCollection,
Other(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AssetAccess {
Read,
Write,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AssetRequirement {
pub asset: AssetId,
pub access: AssetAccess,
}
pub(crate) fn validate(plan: &Plan) -> Result<(), ValidationError> {
let mut keys = BTreeSet::new();
for (id, r) in &plan.assets {
if id != &r.id
|| r.key.0.trim().is_empty()
|| r.key.0.trim() != r.key.0
|| !keys.insert(&r.key)
|| r.label.trim().is_empty()
{
return Err(invalid(
"asset",
id,
"asset id, unique key and label are required",
));
}
match &r.kind {
AssetKind::Other(name) if name.trim().is_empty() => {
return Err(invalid("asset kind", id, "category cannot be empty"));
}
AssetKind::GitRepository { remotes } if remotes.iter().any(|r| r.trim().is_empty()) => {
return Err(invalid("asset remote", id, "address cannot be empty"));
}
AssetKind::GitRepository { remotes }
if remotes
.iter()
.any(|r| crate::credentials::check_remote(r).is_err()) =>
{
return Err(invalid(
"asset remote",
id,
"shared plans cannot carry remote credentials",
));
}
_ => {}
}
}
for work in plan.work_items.values() {
let mut seen = BTreeSet::new();
for requirement in &work.contract.assets {
if !plan.assets.contains_key(&requirement.asset) || !seen.insert(requirement.asset) {
return Err(invalid(
"work asset",
work.id,
format!("missing or repeated asset {}", requirement.asset),
));
}
}
}
Ok(())
}
#[cfg(test)]
mod tests;