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