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