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