systemprompt_cli/
context.rs1use std::sync::Arc;
14
15use anyhow::{Context, Result, bail};
16use systemprompt_database::DbPool;
17use systemprompt_runtime::{AppContext, DatabaseContext};
18use tokio::sync::OnceCell;
19
20use crate::cli_settings::CliConfig;
21use crate::env_overrides::EnvOverrides;
22use crate::interactive::{DialoguerPrompter, Prompter};
23
24pub struct CommandContext {
25 pub cli: CliConfig,
26 pub env: EnvOverrides,
27 prompter: Box<dyn Prompter>,
28 runtime: OnceCell<Arc<AppContext>>,
29 db: Option<DatabaseContext>,
30 database_url: Option<String>,
31}
32
33impl CommandContext {
34 #[must_use]
35 pub fn new(cli: CliConfig, env: EnvOverrides) -> Self {
36 Self {
37 cli,
38 env,
39 prompter: Box::new(DialoguerPrompter),
40 runtime: OnceCell::new(),
41 db: None,
42 database_url: None,
43 }
44 }
45
46 #[must_use]
47 pub fn with_database(
48 cli: CliConfig,
49 env: EnvOverrides,
50 db: DatabaseContext,
51 database_url: String,
52 ) -> Self {
53 Self {
54 cli,
55 env,
56 prompter: Box::new(DialoguerPrompter),
57 runtime: OnceCell::new(),
58 db: Some(db),
59 database_url: Some(database_url),
60 }
61 }
62
63 #[must_use]
64 pub fn with_app_context(cli: CliConfig, env: EnvOverrides, app: Arc<AppContext>) -> Self {
65 Self {
66 cli,
67 env,
68 prompter: Box::new(DialoguerPrompter),
69 runtime: OnceCell::new_with(Some(app)),
70 db: None,
71 database_url: None,
72 }
73 }
74
75 #[must_use]
76 pub fn with_prompter(mut self, prompter: Box<dyn Prompter>) -> Self {
77 self.prompter = prompter;
78 self
79 }
80
81 #[must_use]
82 pub fn prompter(&self) -> &dyn Prompter {
83 self.prompter.as_ref()
84 }
85
86 #[must_use]
87 pub const fn is_database_scoped(&self) -> bool {
88 self.db.is_some()
89 }
90
91 #[must_use]
92 pub fn database_url(&self) -> Option<&str> {
93 self.database_url.as_deref()
94 }
95
96 #[must_use]
97 pub const fn database_context(&self) -> Option<&DatabaseContext> {
98 self.db.as_ref()
99 }
100
101 pub async fn app_context(&self) -> Result<&Arc<AppContext>> {
102 if self.db.is_some() {
103 bail!("This command requires full profile initialization. Remove --database-url flag.");
104 }
105 self.runtime
106 .get_or_try_init(|| async { AppContext::new().await.map(Arc::new) })
107 .await
108 .context("Failed to initialize application context")
109 }
110
111 pub async fn db_pool(&self) -> Result<DbPool> {
112 if let Some(db) = &self.db {
113 return Ok(db.db_pool_arc());
114 }
115 Ok(Arc::clone(self.app_context().await?.db_pool()))
116 }
117
118 pub async fn database(&self) -> Result<DatabaseContext> {
119 if let Some(db) = &self.db {
120 return Ok(db.clone());
121 }
122 Ok(DatabaseContext::from_pool(Arc::clone(
123 self.app_context().await?.db_pool(),
124 )))
125 }
126}
127
128impl std::fmt::Debug for CommandContext {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 f.debug_struct("CommandContext")
131 .field("cli", &self.cli)
132 .field("env", &self.env)
133 .field("database_scoped", &self.db.is_some())
134 .finish_non_exhaustive()
135 }
136}