posthog_cli/experimental/query/
command.rs1use anyhow::Error;
2use clap::Subcommand;
3use miette::{Diagnostic, SourceSpan};
4
5use crate::{
6 experimental::{
7 query::{check_query, run_query, MetadataResponse, Notice},
8 tui::query::start_query_editor,
9 },
10 invocation_context::context,
11};
12
13#[derive(Debug, Subcommand)]
14pub enum QueryCommand {
15 Editor {
17 #[arg(long, default_value = "false")]
18 no_print: bool,
20 #[arg(long, default_value = "false")]
21 debug: bool,
23 #[arg(long, default_value = "false")]
24 execute: bool,
26 },
27 Run {
29 query: String,
31 #[arg(long)]
32 debug: bool,
34 },
35 Check {
37 query: String,
39 #[arg(long)]
41 raw: bool,
42 },
43}
44
45pub fn query_command(query: &QueryCommand) -> Result<(), Error> {
46 let creds = context().token.clone();
47 let host = creds.get_host();
48
49 match query {
50 QueryCommand::Editor {
51 no_print,
52 debug,
53 execute,
54 } => {
55 context().capture_command_invoked("query_editor");
57 let res = start_query_editor(&host, creds.clone(), *debug)?;
58 if !no_print {
59 println!("Final query: {res}");
60 }
61 if *execute {
62 let query_endpoint = format!("{}/api/environments/{}/query", host, creds.env_id);
63 let res = run_query(&query_endpoint, &creds.token, &res)??;
64 for result in res.results {
65 println!("{}", serde_json::to_string(&result)?);
66 }
67 }
68 }
69 QueryCommand::Run { query, debug } => {
70 context().capture_command_invoked("query_run");
72 let query_endpoint = format!("{}/api/environments/{}/query", host, creds.env_id);
73 let res = run_query(&query_endpoint, &creds.token, query)??;
74 if *debug {
75 println!("{}", serde_json::to_string_pretty(&res)?);
76 } else {
77 for result in res.results {
78 println!("{}", serde_json::to_string(&result)?);
79 }
80 }
81 }
82 QueryCommand::Check { query, raw } => {
83 context().capture_command_invoked("query_check");
84 let query_endpoint = format!("{}/api/environments/{}/query", host, creds.env_id);
85 let res = check_query(&query_endpoint, &creds.token, query)?;
86 if *raw {
87 println!("{}", serde_json::to_string_pretty(&res)?);
88 } else {
89 pretty_print_check_response(query, res)?;
90 }
91 }
92 }
93
94 Ok(())
95}
96
97#[derive(thiserror::Error, Debug, Diagnostic)]
98#[error("Query checked")]
99#[diagnostic()]
100struct CheckDiagnostic {
101 #[source_code]
102 source_code: String,
103
104 #[related]
105 errors: Vec<CheckError>,
106 #[related]
107 warnings: Vec<CheckWarning>,
108 #[related]
109 notices: Vec<CheckNotice>,
110}
111
112#[derive(thiserror::Error, Debug, Diagnostic)]
113#[error("Error")]
114#[diagnostic(severity(Error))]
115struct CheckError {
116 #[help]
117 message: String,
118 #[label]
119 err_span: SourceSpan,
120}
121
122#[derive(thiserror::Error, Debug, Diagnostic)]
123#[error("Warning")]
124#[diagnostic(severity(Warning))]
125struct CheckWarning {
126 #[help]
127 message: String,
128 #[label]
129 err_span: SourceSpan,
130}
131
132#[derive(thiserror::Error, Debug, Diagnostic)]
133#[error("Notice")]
134#[diagnostic(severity(Info))]
135struct CheckNotice {
136 #[help]
137 message: String,
138 #[label]
139 err_span: SourceSpan,
140}
141
142fn pretty_print_check_response(query: &str, res: MetadataResponse) -> Result<(), Error> {
144 let errors = res.errors.into_iter().map(CheckError::from).collect();
145 let warnings = res.warnings.into_iter().map(CheckWarning::from).collect();
146 let notices = res.notices.into_iter().map(CheckNotice::from).collect();
147
148 let diagnostic: miette::Error = CheckDiagnostic {
149 source_code: query.to_string(),
150 errors,
151 warnings,
152 notices,
153 }
154 .into();
155
156 println!("{diagnostic:?}");
157
158 Ok(())
159}
160
161impl From<Notice> for CheckNotice {
162 fn from(notice: Notice) -> Self {
163 let (start, len) = match notice.span {
164 Some(span) => (span.start, span.end - span.start),
165 None => (0, 0),
166 };
167 Self {
168 message: notice.message,
169 err_span: SourceSpan::new(start.into(), len),
170 }
171 }
172}
173
174impl From<Notice> for CheckWarning {
175 fn from(notice: Notice) -> Self {
176 let (start, len) = match notice.span {
177 Some(span) => (span.start, span.end - span.start),
178 None => (0, 0),
179 };
180 Self {
181 message: notice.message,
182 err_span: SourceSpan::new(start.into(), len),
183 }
184 }
185}
186
187impl From<Notice> for CheckError {
188 fn from(notice: Notice) -> Self {
189 let (start, len) = match notice.span {
190 Some(span) => (span.start, span.end - span.start),
191 None => (0, 0),
192 };
193 Self {
194 message: notice.message,
195 err_span: SourceSpan::new(start.into(), len),
196 }
197 }
198}