systemprompt_models/managed/
manifest.rs1use serde::{Deserialize, Serialize};
7use std::collections::{BTreeMap, BTreeSet};
8use systemprompt_identifiers::{ResourceRevisionId, SourceSnapshotId};
9
10use super::assets::{AssetDigest, RevisionFiles};
11use super::error::{RevisionBundleError, invalid};
12
13type Result<T> = std::result::Result<T, RevisionBundleError>;
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
16#[serde(deny_unknown_fields)]
17pub struct FileEntry {
18 pub digest: AssetDigest,
19 pub bytes: u64,
20 pub media_type: String,
21 pub executable: bool,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
25#[serde(deny_unknown_fields)]
26pub struct DependencyRef {
27 pub revision_id: ResourceRevisionId,
28 pub digest: AssetDigest,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
32#[serde(deny_unknown_fields)]
33pub struct RevisionManifest {
34 pub schema_version: u32,
35 pub snapshot_id: SourceSnapshotId,
36 pub parent_id: Option<ResourceRevisionId>,
37 pub files: BTreeMap<String, FileEntry>,
38 pub dependencies: BTreeMap<String, DependencyRef>,
39}
40
41impl RevisionManifest {
42 pub fn from_files(
43 snapshot_id: SourceSnapshotId,
44 parent_id: Option<ResourceRevisionId>,
45 files: &RevisionFiles,
46 dependencies: BTreeMap<String, DependencyRef>,
47 ) -> Result<Self> {
48 files.validate()?;
49 if dependencies.len() > 256 {
50 return Err(invalid("Revision has more than 256 dependencies"));
51 }
52 let mut ids = BTreeSet::new();
53 for (key, dependency) in &dependencies {
54 validate_key(key)?;
55 if !ids.insert(dependency.revision_id.as_str()) {
56 return Err(invalid("Duplicate dependency revision"));
57 }
58 }
59 Ok(Self {
60 schema_version: 1,
61 snapshot_id,
62 parent_id,
63 files: files
64 .0
65 .iter()
66 .map(|(path, file)| {
67 (
68 path.clone(),
69 FileEntry {
70 digest: AssetDigest::of(&file.bytes),
71 bytes: file.bytes.len() as u64,
72 media_type: file.media_type.clone(),
73 executable: file.executable,
74 },
75 )
76 })
77 .collect(),
78 dependencies,
79 })
80 }
81
82 pub fn digest(&self) -> Result<AssetDigest> {
83 Ok(AssetDigest::of(&serde_jcs::to_vec(self)?))
84 }
85}
86
87pub fn validate_key(key: &str) -> Result<()> {
88 if key.is_empty()
89 || key.len() > 200
90 || !key
91 .bytes()
92 .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.' | b'/'))
93 {
94 return Err(invalid("Invalid resource key"));
95 }
96 Ok(())
97}