1use serde::{Deserialize, Serialize};
4use std::fs;
5use std::path::Path;
6use thiserror::Error;
7
8pub const LOCK_VERSION: u32 = 1;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct SealedSignature {
12 pub name: String,
13 pub rust_signature: String,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct ResolvedDependency {
18 pub name: String,
19 pub version: String,
20 #[serde(default)]
21 pub rust: bool,
22 #[serde(default, skip_serializing_if = "Vec::is_empty")]
23 pub features: Vec<String>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub path: Option<String>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct CrispLock {
31 pub version: u32,
32 #[serde(default, skip_serializing_if = "Vec::is_empty")]
33 pub dependencies: Vec<ResolvedDependency>,
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
35 pub sealed_api: Vec<SealedSignature>,
36}
37
38impl Default for CrispLock {
39 fn default() -> Self {
40 Self {
41 version: LOCK_VERSION,
42 dependencies: Vec::new(),
43 sealed_api: Vec::new(),
44 }
45 }
46}
47
48#[derive(Debug, Error)]
49pub enum LockError {
50 #[error("failed to read crisp.lock: {0}")]
51 Io(#[from] std::io::Error),
52 #[error("failed to parse crisp.lock: {0}")]
53 Parse(#[from] serde_json::Error),
54 #[error("unsupported crisp.lock version {0} (expected {LOCK_VERSION})")]
55 UnsupportedVersion(u32),
56}
57
58pub fn read_lock(crate_root: &Path) -> Result<Option<CrispLock>, LockError> {
59 let path = crate_root.join("crisp.lock");
60 if !path.is_file() {
61 return Ok(None);
62 }
63 let raw = fs::read_to_string(&path)?;
64 let lock: CrispLock = serde_json::from_str(&raw)?;
65 if lock.version != LOCK_VERSION {
66 return Err(LockError::UnsupportedVersion(lock.version));
67 }
68 Ok(Some(lock))
69}
70
71pub fn write_lock(crate_root: &Path, lock: &CrispLock) -> Result<(), LockError> {
72 let path = crate_root.join("crisp.lock");
73 let raw = serde_json::to_string_pretty(lock)?;
74 fs::write(path, raw)?;
75 Ok(())
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use tempfile::TempDir;
82
83 #[test]
84 fn roundtrip_lock() {
85 let dir = TempDir::new().unwrap();
86 let lock = CrispLock {
87 version: LOCK_VERSION,
88 dependencies: vec![ResolvedDependency {
89 name: "tokio".into(),
90 version: "1".into(),
91 rust: true,
92 features: vec!["rt".into(), "macros".into()],
93 path: None,
94 }],
95 sealed_api: vec![SealedSignature {
96 name: "main::main".into(),
97 rust_signature: "pub fn main() -> ()".into(),
98 }],
99 };
100 write_lock(dir.path(), &lock).unwrap();
101 let read = read_lock(dir.path()).unwrap().expect("lock");
102 assert_eq!(read, lock);
103 }
104}