Skip to main content

systemprompt_cloud/paths/
cloud.rs

1//! Typed paths of the cloud control-plane API.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use std::path::{Path, PathBuf};
7
8use crate::constants::{cli_session, credentials, dir_names, operator, tenants};
9
10use super::{ProjectContext, resolve_path};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum CloudPath {
14    Credentials,
15    OperatorCredentials,
16    Tenants,
17    CliSession,
18    SessionsDir,
19}
20
21impl CloudPath {
22    #[must_use]
23    pub const fn default_filename(&self) -> &'static str {
24        match self {
25            Self::Credentials => credentials::DEFAULT_FILE_NAME,
26            Self::OperatorCredentials => operator::DEFAULT_FILE_NAME,
27            Self::Tenants => tenants::DEFAULT_FILE_NAME,
28            Self::CliSession => cli_session::DEFAULT_FILE_NAME,
29            Self::SessionsDir => dir_names::SESSIONS,
30        }
31    }
32
33    #[must_use]
34    pub const fn default_dirname(&self) -> &'static str {
35        match self {
36            Self::Credentials => credentials::DEFAULT_DIR_NAME,
37            Self::OperatorCredentials => operator::DEFAULT_DIR_NAME,
38            Self::Tenants => tenants::DEFAULT_DIR_NAME,
39            Self::CliSession | Self::SessionsDir => cli_session::DEFAULT_DIR_NAME,
40        }
41    }
42
43    #[must_use]
44    pub const fn is_dir(&self) -> bool {
45        matches!(self, Self::SessionsDir)
46    }
47}
48
49#[derive(Debug, Clone)]
50pub struct CloudPaths {
51    base_dir: PathBuf,
52    credentials_path: Option<PathBuf>,
53    tenants_path: Option<PathBuf>,
54}
55
56impl CloudPaths {
57    #[must_use]
58    pub fn new(profile_dir: &Path) -> Self {
59        Self {
60            base_dir: profile_dir.join(credentials::DEFAULT_DIR_NAME),
61            credentials_path: None,
62            tenants_path: None,
63        }
64    }
65
66    #[must_use]
67    pub fn from_project_context(ctx: &ProjectContext) -> Self {
68        Self {
69            base_dir: ctx.systemprompt_dir(),
70            credentials_path: Some(ctx.local_credentials()),
71            tenants_path: Some(ctx.local_tenants()),
72        }
73    }
74
75    #[must_use]
76    pub fn from_config(
77        profile_dir: &Path,
78        credentials_path_str: &str,
79        tenants_path_str: &str,
80    ) -> Self {
81        let credentials_path = if credentials_path_str.is_empty() {
82            None
83        } else {
84            Some(resolve_path(profile_dir, credentials_path_str))
85        };
86
87        let tenants_path = if tenants_path_str.is_empty() {
88            None
89        } else {
90            Some(resolve_path(profile_dir, tenants_path_str))
91        };
92
93        let base_dir = credentials_path
94            .as_ref()
95            .and_then(|p| p.parent())
96            .map_or_else(
97                || {
98                    profile_dir
99                        .ancestors()
100                        .find(|p| p.file_name().is_some_and(|n| n == ".systemprompt"))
101                        .map_or_else(
102                            || profile_dir.join(credentials::DEFAULT_DIR_NAME),
103                            PathBuf::from,
104                        )
105                },
106                PathBuf::from,
107            );
108
109        Self {
110            base_dir,
111            credentials_path,
112            tenants_path,
113        }
114    }
115
116    #[must_use]
117    pub fn resolve(&self, path: CloudPath) -> PathBuf {
118        match path {
119            CloudPath::Credentials => self
120                .credentials_path
121                .clone()
122                .unwrap_or_else(|| self.base_dir.join(credentials::DEFAULT_FILE_NAME)),
123            CloudPath::OperatorCredentials => self.base_dir.join(operator::DEFAULT_FILE_NAME),
124            CloudPath::Tenants => self
125                .tenants_path
126                .clone()
127                .unwrap_or_else(|| self.base_dir.join(tenants::DEFAULT_FILE_NAME)),
128            CloudPath::CliSession => self.base_dir.join(cli_session::DEFAULT_FILE_NAME),
129            CloudPath::SessionsDir => self.base_dir.join(dir_names::SESSIONS),
130        }
131    }
132
133    #[must_use]
134    pub fn base_dir(&self) -> &Path {
135        &self.base_dir
136    }
137
138    #[must_use]
139    pub fn exists(&self, path: CloudPath) -> bool {
140        self.resolve(path).exists()
141    }
142}
143
144#[must_use]
145pub fn get_cloud_paths() -> CloudPaths {
146    let project_ctx = ProjectContext::discover();
147    CloudPaths::from_project_context(&project_ctx)
148}