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