Skip to main content

strop_engine/session/
trust.rs

1//! Project command trust uses the same private, lossless state boundary.
2use super::{persistence, SessionError};
3use crate::files::FileTarget;
4use std::path::{Path, PathBuf};
5
6fn path(base: Option<&Path>) -> Option<PathBuf> {
7    base.map(|base| base.join("strop").join("trusted-projects"))
8}
9
10fn load(path: &Path) -> Result<Vec<FileTarget>, SessionError> {
11    let bytes = match persistence::read(path) {
12        Ok(bytes) => bytes,
13        Err(SessionError::Io { source, .. }) if source.kind() == std::io::ErrorKind::NotFound => {
14            return Ok(Vec::new())
15        }
16        Err(error) => return Err(error),
17    };
18    if bytes.first() == Some(&b'[') {
19        return Ok(serde_json::from_slice(&bytes)?);
20    }
21    // Legacy line-based stores only had unambiguous UTF-8 identities.
22    let text = std::str::from_utf8(&bytes).map_err(|e| SessionError::Invalid(e.to_string()))?;
23    text.lines()
24        .map(|line| {
25            if line.contains('\u{fffd}') {
26                return Err(strop_core::path_serde::PathError::AmbiguousLegacy.into());
27            }
28            let root = PathBuf::from(line);
29            strop_core::path_serde::validate(&root)?;
30            Ok(FileTarget::Local(root))
31        })
32        .collect()
33}
34
35pub fn is_trusted(base: Option<&Path>, root: &Path) -> Result<bool, SessionError> {
36    let Some(path) = path(base) else {
37        return Ok(false);
38    };
39    Ok(load(&path)?
40        .iter()
41        .any(|entry| matches!(entry, FileTarget::Local(path) if path == root)))
42}
43
44pub fn trust(base: Option<&Path>, root: &Path) -> Result<(), SessionError> {
45    strop_core::path_serde::validate(root)?;
46    save(base, FileTarget::Local(root.to_owned()))
47}
48
49pub fn is_trusted_remote(
50    base: Option<&Path>,
51    endpoint: &strop_workspace::RemoteEndpoint,
52    root: &Path,
53) -> Result<bool, SessionError> {
54    let Some(path) = path(base) else {
55        return Ok(false);
56    };
57    let file = strop_workspace::RemoteFile::from_path(endpoint.clone(), root.to_owned())
58        .map_err(|error| SessionError::Invalid(error.to_string()))?;
59    let target = FileTarget::Remote(file.into());
60    Ok(load(&path)?.contains(&target))
61}
62pub fn trust_remote(
63    base: Option<&Path>,
64    root: &strop_workspace::RemoteFile,
65) -> Result<(), SessionError> {
66    save(base, FileTarget::Remote(root.clone().into()))
67}
68fn save(base: Option<&Path>, target: FileTarget) -> Result<(), SessionError> {
69    let path = path(base).ok_or_else(|| {
70        SessionError::Invalid("state directory unavailable; trust was not saved".into())
71    })?;
72    let mut entries = load(&path)?;
73    if !entries.contains(&target) {
74        entries.push(target);
75        persistence::write(&path, &entries)?;
76    }
77    Ok(())
78}