1use std::path::PathBuf;
4
5use crate::{
6 engine::new_zoo_client,
7 errors::ExecErrorWithState,
8 execution::{EnvironmentRef, ExecState, ExecutorContext, ExecutorSettings},
9 ConnectionError, ExecError, KclError, KclErrorWithOutputs, Program,
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<(ExecState, EnvironmentRef, image::DynamicImage, Option<Vec<u8>>), ExecErrorWithState> {
40 let ctx = new_context(true, current_file).await?;
41 let (exec_state, env, img) = match do_execute_and_snapshot(&ctx, ast).await {
42 Ok((exec_state, env_ref, img)) => (exec_state, env_ref, img),
43 Err(err) => {
44 ctx.close().await;
47 return Err(err);
48 }
49 };
50 let mut step = None;
51 if with_export_step {
52 let files = match ctx.export_step(true).await {
53 Ok(f) => f,
54 Err(err) => {
55 ctx.close().await;
57 return Err(ExecErrorWithState::new(
58 ExecError::BadExport(format!("Export failed: {:?}", err)),
59 exec_state.clone(),
60 ));
61 }
62 };
63
64 step = files.into_iter().next().map(|f| f.contents);
65 }
66 ctx.close().await;
67 Ok((exec_state, env, img, step))
68}
69
70pub async fn execute_and_snapshot_no_auth(
71 code: &str,
72 current_file: Option<PathBuf>,
73) -> Result<(image::DynamicImage, EnvironmentRef), ExecError> {
74 let ctx = new_context(false, current_file).await?;
75 let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
76 let res = do_execute_and_snapshot(&ctx, program)
77 .await
78 .map(|(_, env_ref, snap)| (snap, env_ref))
79 .map_err(|err| err.error);
80 ctx.close().await;
81 res
82}
83
84async fn do_execute_and_snapshot(
85 ctx: &ExecutorContext,
86 program: Program,
87) -> Result<(ExecState, EnvironmentRef, image::DynamicImage), ExecErrorWithState> {
88 let mut exec_state = ExecState::new(ctx);
89 let result = ctx
90 .run(&program, &mut exec_state)
91 .await
92 .map_err(|err| ExecErrorWithState::new(err.into(), exec_state.clone()))?;
93 for e in exec_state.errors() {
94 if e.severity.is_err() {
95 return Err(ExecErrorWithState::new(
96 KclErrorWithOutputs::no_outputs(KclError::Semantic(e.clone().into())).into(),
97 exec_state.clone(),
98 ));
99 }
100 }
101 let snapshot_png_bytes = ctx
102 .prepare_snapshot()
103 .await
104 .map_err(|err| ExecErrorWithState::new(err, exec_state.clone()))?
105 .contents
106 .0;
107
108 let img = image::ImageReader::new(std::io::Cursor::new(snapshot_png_bytes))
110 .with_guessed_format()
111 .map_err(|e| ExecError::BadPng(e.to_string()))
112 .and_then(|x| x.decode().map_err(|e| ExecError::BadPng(e.to_string())))
113 .map_err(|err| ExecErrorWithState::new(err, exec_state.clone()))?;
114
115 Ok((exec_state, result.0, img))
116}
117
118pub async fn new_context(with_auth: bool, current_file: Option<PathBuf>) -> Result<ExecutorContext, ConnectionError> {
119 let mut client = new_zoo_client(if with_auth { None } else { Some("bad_token".to_string()) }, None)
120 .map_err(ConnectionError::CouldNotMakeClient)?;
121 if !with_auth {
122 client.set_base_url("https://api.zoo.dev".to_string());
126 }
127
128 let mut settings = ExecutorSettings {
129 highlight_edges: true,
130 enable_ssao: false,
131 show_grid: false,
132 replay: None,
133 project_directory: None,
134 current_file: None,
135 };
136 if let Some(current_file) = current_file {
137 settings.with_current_file(crate::TypedPath(current_file));
138 }
139 let ctx = ExecutorContext::new(&client, settings)
140 .await
141 .map_err(ConnectionError::Establishing)?;
142 Ok(ctx)
143}
144
145pub async fn execute_and_export_step(
146 code: &str,
147 current_file: Option<PathBuf>,
148) -> Result<
149 (
150 ExecState,
151 EnvironmentRef,
152 Vec<kittycad_modeling_cmds::websocket::RawFile>,
153 ),
154 ExecErrorWithState,
155> {
156 let ctx = new_context(true, current_file).await?;
157 let mut exec_state = ExecState::new(&ctx);
158 let program = Program::parse_no_errs(code)
159 .map_err(|err| ExecErrorWithState::new(KclErrorWithOutputs::no_outputs(err).into(), exec_state.clone()))?;
160 let result = ctx
161 .run(&program, &mut exec_state)
162 .await
163 .map_err(|err| ExecErrorWithState::new(err.into(), exec_state.clone()))?;
164 for e in exec_state.errors() {
165 if e.severity.is_err() {
166 return Err(ExecErrorWithState::new(
167 KclErrorWithOutputs::no_outputs(KclError::Semantic(e.clone().into())).into(),
168 exec_state.clone(),
169 ));
170 }
171 }
172
173 let files = match ctx.export_step(true).await {
174 Ok(f) => f,
175 Err(err) => {
176 return Err(ExecErrorWithState::new(
177 ExecError::BadExport(format!("Export failed: {:?}", err)),
178 exec_state.clone(),
179 ));
180 }
181 };
182
183 ctx.close().await;
184
185 Ok((exec_state, result.0, files))
186}