kcl_lib/
test_server.rs

1//! Types used to send data to the test server.
2
3use 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
20/// Executes a kcl program and takes a snapshot of the result.
21/// This returns the bytes of the snapshot.
22pub 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
37/// Executes a kcl program and takes a snapshot of the result.
38/// This returns the bytes of the snapshot.
39pub 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    // Decode the snapshot, return it.
105    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        // Use prod, don't override based on env vars.
123        // We do this so even in the engine repo, tests that need to run with
124        // no auth can fail in the same way as they would in prod.
125        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}