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