1use crate::{AssetId, Key, Plan, ValidationError, validation::invalid};
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeSet;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(deny_unknown_fields)]
8pub struct WorkspaceAsset {
9 pub id: AssetId,
11 pub key: Key,
13 pub label: String,
15 pub kind: AssetKind,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(deny_unknown_fields)]
22pub enum AssetKind {
23 GitRepository {
25 remotes: Vec<String>,
27 },
28 Folder,
30 DocumentCollection,
32 Other(String),
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub enum AssetAccess {
39 Read,
41 Write,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(deny_unknown_fields)]
48pub struct AssetRequirement {
49 pub asset: AssetId,
51 pub access: AssetAccess,
53}
54
55pub(crate) fn validate(plan: &Plan) -> Result<(), ValidationError> {
56 let mut keys = BTreeSet::new();
57 for (id, r) in &plan.assets {
58 if id != &r.id
59 || r.key.0.trim().is_empty()
60 || r.key.0.trim() != r.key.0
61 || !keys.insert(&r.key)
62 || r.label.trim().is_empty()
63 {
64 return Err(invalid(
65 "asset",
66 id,
67 "asset id, unique key and label are required",
68 ));
69 }
70 match &r.kind {
71 AssetKind::Other(name) if name.trim().is_empty() => {
72 return Err(invalid("asset kind", id, "category cannot be empty"));
73 }
74 AssetKind::GitRepository { remotes } if remotes.iter().any(|r| r.trim().is_empty()) => {
75 return Err(invalid("asset remote", id, "address cannot be empty"));
76 }
77 AssetKind::GitRepository { remotes }
78 if remotes
79 .iter()
80 .any(|r| crate::credentials::check_remote(r).is_err()) =>
81 {
82 return Err(invalid(
83 "asset remote",
84 id,
85 "shared plans cannot carry remote credentials",
86 ));
87 }
88 _ => {}
89 }
90 }
91 for work in plan.work_items.values() {
92 let mut seen = BTreeSet::new();
93 for requirement in &work.contract.assets {
94 if !plan.assets.contains_key(&requirement.asset) || !seen.insert(requirement.asset) {
95 return Err(invalid(
96 "work asset",
97 work.id,
98 format!("missing or repeated asset {}", requirement.asset),
99 ));
100 }
101 }
102 }
103 Ok(())
104}
105
106#[cfg(test)]
107mod tests;