1use std::path::PathBuf;
4
5use kittycad_modeling_cmds::websocket::RawFile;
6
7use crate::ConnectionError;
8use crate::ExecError;
9use crate::KclError;
10use crate::KclErrorWithOutputs;
11use crate::Program;
12use crate::engine::new_zoo_client;
13use crate::errors::ExecErrorWithState;
14use crate::execution::EnvironmentRef;
15use crate::execution::ExecState;
16use crate::execution::ExecutorContext;
17use crate::execution::ExecutorSettings;
18
19#[derive(serde::Deserialize, serde::Serialize)]
20pub struct RequestBody {
21 pub kcl_program: String,
22 #[serde(default)]
23 pub test_name: String,
24}
25
26pub async fn execute_and_snapshot(code: &str, current_file: Option<PathBuf>) -> Result<image::DynamicImage, ExecError> {
29 let ctx = new_context(true, current_file).await?;
30 let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
31 let res = do_execute_and_snapshot(&ctx, program)
32 .await
33 .map(|(_, _, snap)| snap)
34 .map_err(|err| err.error);
35 ctx.close().await;
36 res
37}
38
39pub struct Snapshot3d {
40 pub image: image::DynamicImage,
42 pub gltf: Vec<RawFile>,
44}
45
46pub async fn execute_and_snapshot_3d(code: &str, current_file: Option<PathBuf>) -> Result<Snapshot3d, ExecError> {
48 let ctx = new_context(true, current_file).await?;
49 let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
50 let image = do_execute_and_snapshot(&ctx, program)
51 .await
52 .map(|(_, _, snap)| snap)
53 .map_err(|err| err.error)?;
54 let gltf_res = ctx
55 .export(kittycad_modeling_cmds::format::OutputFormat3d::Gltf(Default::default()))
56 .await;
57 let gltf = match gltf_res {
58 Err(err) if err.message() == "Nothing to export" => Vec::new(),
59 Err(err) => {
60 eprintln!("Error exporting: {}", err.message());
61 Vec::new()
62 }
63 Ok(x) => x,
64 };
65 ctx.close().await;
66 Ok(Snapshot3d { image, gltf })
67}
68#[cfg(test)]
71pub async fn execute_and_snapshot_ast(
72 ast: Program,
73 current_file: Option<PathBuf>,
74 with_export_step: bool,
75) -> Result<
76 (
77 ExecState,
78 ExecutorContext,
79 EnvironmentRef,
80 image::DynamicImage,
81 Option<Vec<u8>>,
82 ),
83 ExecErrorWithState,
84> {
85 let ctx = new_context(true, current_file).await?;
86 let (exec_state, env, img) = match do_execute_and_snapshot(&ctx, ast).await {
87 Ok((exec_state, env_ref, img)) => (exec_state, env_ref, img),
88 Err(err) => {
89 ctx.close().await;
92 return Err(err);
93 }
94 };
95 let mut step = None;
96 if with_export_step {
97 let files = match ctx.export_step(true).await {
98 Ok(f) => f,
99 Err(err) => {
100 ctx.close().await;
102 return Err(ExecErrorWithState::new(
103 ExecError::BadExport(format!("Export failed: {err:?}")),
104 exec_state.clone(),
105 None,
106 ));
107 }
108 };
109
110 step = files.into_iter().next().map(|f| f.contents);
111 }
112 ctx.close().await;
113 Ok((exec_state, ctx, env, img, step))
114}
115
116pub async fn execute_and_snapshot_no_auth(
117 code: &str,
118 current_file: Option<PathBuf>,
119) -> Result<(image::DynamicImage, EnvironmentRef), ExecError> {
120 let ctx = new_context(false, current_file).await?;
121 let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
122 let res = do_execute_and_snapshot(&ctx, program)
123 .await
124 .map(|(_, env_ref, snap)| (snap, env_ref))
125 .map_err(|err| err.error);
126 ctx.close().await;
127 res
128}
129
130async fn do_execute_and_snapshot(
131 ctx: &ExecutorContext,
132 program: Program,
133) -> Result<(ExecState, EnvironmentRef, image::DynamicImage), ExecErrorWithState> {
134 let mut exec_state = ExecState::new(ctx);
135 let result = ctx.run(&program, &mut exec_state).await;
136 let responses = if result.is_err() {
137 #[cfg(all(feature = "artifact-graph", feature = "snapshot-engine-responses"))]
138 {
139 Some(exec_state.take_root_module_responses())
140 }
141 #[cfg(not(all(feature = "artifact-graph", feature = "snapshot-engine-responses")))]
142 None
143 } else {
144 None
145 };
146 let result = result.map_err(|err| ExecErrorWithState::new(err.into(), exec_state.clone(), responses))?;
147 for issue in exec_state.issues() {
148 if issue.severity.is_err() {
149 return Err(ExecErrorWithState::new(
150 KclErrorWithOutputs::no_outputs(KclError::new_semantic(issue.clone().into())).into(),
151 exec_state.clone(),
152 None,
153 ));
154 }
155 }
156 let snapshot_png_bytes = ctx
157 .prepare_snapshot()
158 .await
159 .map_err(|err| ExecErrorWithState::new(err, exec_state.clone(), None))?
160 .contents
161 .0;
162
163 let img = image::ImageReader::new(std::io::Cursor::new(snapshot_png_bytes))
165 .with_guessed_format()
166 .map_err(|e| ExecError::BadPng(e.to_string()))
167 .and_then(|x| x.decode().map_err(|e| ExecError::BadPng(e.to_string())))
168 .map_err(|err| ExecErrorWithState::new(err, exec_state.clone(), None))?;
169
170 Ok((exec_state, result.0, img))
171}
172
173pub async fn new_context(with_auth: bool, current_file: Option<PathBuf>) -> Result<ExecutorContext, ConnectionError> {
174 let mut client = new_zoo_client(if with_auth { None } else { Some("bad_token".to_string()) }, None)
175 .map_err(ConnectionError::CouldNotMakeClient)?;
176 if !with_auth {
177 client.set_base_url("https://api.zoo.dev".to_string());
181 }
182
183 let mut settings = ExecutorSettings {
184 highlight_edges: true,
185 enable_ssao: false,
186 show_grid: false,
187 replay: None,
188 project_directory: None,
189 current_file: None,
190 fixed_size_grid: true,
191 };
192 if let Some(current_file) = current_file {
193 settings.with_current_file(crate::TypedPath(current_file));
194 }
195 let ctx = ExecutorContext::new(&client, settings)
196 .await
197 .map_err(ConnectionError::Establishing)?;
198 Ok(ctx)
199}
200
201pub async fn execute_and_export_step(
202 code: &str,
203 current_file: Option<PathBuf>,
204) -> Result<
205 (
206 ExecState,
207 EnvironmentRef,
208 Vec<kittycad_modeling_cmds::websocket::RawFile>,
209 ),
210 ExecErrorWithState,
211> {
212 let ctx = new_context(true, current_file).await?;
213 let mut exec_state = ExecState::new(&ctx);
214 let program = Program::parse_no_errs(code).map_err(|err| {
215 ExecErrorWithState::new(KclErrorWithOutputs::no_outputs(err).into(), exec_state.clone(), None)
216 })?;
217 let result = ctx
218 .run(&program, &mut exec_state)
219 .await
220 .map_err(|err| ExecErrorWithState::new(err.into(), exec_state.clone(), None))?;
221 for issue in exec_state.issues() {
222 if issue.severity.is_err() {
223 return Err(ExecErrorWithState::new(
224 KclErrorWithOutputs::no_outputs(KclError::new_semantic(issue.clone().into())).into(),
225 exec_state.clone(),
226 None,
227 ));
228 }
229 }
230
231 let files = match ctx.export_step(true).await {
232 Ok(f) => f,
233 Err(err) => {
234 return Err(ExecErrorWithState::new(
235 ExecError::BadExport(format!("Export failed: {err:?}")),
236 exec_state.clone(),
237 None,
238 ));
239 }
240 };
241
242 ctx.close().await;
243
244 Ok((exec_state, result.0, files))
245}