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