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