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) -> Result<(ExecState, EnvironmentRef, image::DynamicImage), ExecErrorWithState> {
44    let ctx = new_context(units, true, current_file).await?;
45    let res = do_execute_and_snapshot(&ctx, ast).await;
46    ctx.close().await;
47    res
48}
49
50pub async fn execute_and_snapshot_no_auth(
51    code: &str,
52    units: UnitLength,
53    current_file: Option<PathBuf>,
54) -> Result<(image::DynamicImage, EnvironmentRef), ExecError> {
55    let ctx = new_context(units, false, current_file).await?;
56    let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
57    let res = do_execute_and_snapshot(&ctx, program)
58        .await
59        .map(|(_, env_ref, snap)| (snap, env_ref))
60        .map_err(|err| err.error);
61    ctx.close().await;
62    res
63}
64
65async fn do_execute_and_snapshot(
66    ctx: &ExecutorContext,
67    program: Program,
68) -> Result<(ExecState, EnvironmentRef, image::DynamicImage), ExecErrorWithState> {
69    let mut exec_state = ExecState::new(&ctx.settings);
70    let result = ctx
71        .run_with_ui_outputs(&program, &mut exec_state)
72        .await
73        .map_err(|err| ExecErrorWithState::new(err.into(), exec_state.clone()))?;
74    for e in exec_state.errors() {
75        if e.severity.is_err() {
76            return Err(ExecErrorWithState::new(
77                KclErrorWithOutputs::no_outputs(KclError::Semantic(e.clone().into())).into(),
78                exec_state.clone(),
79            ));
80        }
81    }
82    let snapshot_png_bytes = ctx
83        .prepare_snapshot()
84        .await
85        .map_err(|err| ExecErrorWithState::new(err, exec_state.clone()))?
86        .contents
87        .0;
88
89    // Decode the snapshot, return it.
90    let img = image::ImageReader::new(std::io::Cursor::new(snapshot_png_bytes))
91        .with_guessed_format()
92        .map_err(|e| ExecError::BadPng(e.to_string()))
93        .and_then(|x| x.decode().map_err(|e| ExecError::BadPng(e.to_string())))
94        .map_err(|err| ExecErrorWithState::new(err, exec_state.clone()))?;
95
96    ctx.close().await;
97
98    Ok((exec_state, result.0, img))
99}
100
101pub async fn new_context(
102    units: UnitLength,
103    with_auth: bool,
104    current_file: Option<PathBuf>,
105) -> Result<ExecutorContext, ConnectionError> {
106    let mut client = new_zoo_client(if with_auth { None } else { Some("bad_token".to_string()) }, None)
107        .map_err(ConnectionError::CouldNotMakeClient)?;
108    if !with_auth {
109        // Use prod, don't override based on env vars.
110        // We do this so even in the engine repo, tests that need to run with
111        // no auth can fail in the same way as they would in prod.
112        client.set_base_url("https://api.zoo.dev".to_string());
113    }
114
115    let mut settings = ExecutorSettings {
116        units,
117        highlight_edges: true,
118        enable_ssao: false,
119        show_grid: false,
120        replay: None,
121        project_directory: None,
122        current_file: None,
123    };
124    if let Some(current_file) = current_file {
125        settings.with_current_file(current_file);
126    }
127    let ctx = ExecutorContext::new(&client, settings)
128        .await
129        .map_err(ConnectionError::Establishing)?;
130    Ok(ctx)
131}