kcl_lib/frontend/
api.rs

1//! An API for controlling the KCL interpreter from the frontend.
2
3#![allow(async_fn_in_trait)]
4
5use kcl_error::SourceRange;
6use serde::{Deserialize, Serialize};
7
8pub use crate::ExecutorSettings as Settings;
9use crate::{ExecOutcome, engine::PlaneName, execution::ArtifactId, pretty::NumericSuffix};
10
11pub trait LifecycleApi {
12    async fn open_project(&self, project: ProjectId, files: Vec<File>, open_file: FileId) -> Result<()>;
13    async fn add_file(&self, project: ProjectId, file: File) -> Result<()>;
14    async fn remove_file(&self, project: ProjectId, file: FileId) -> Result<()>;
15    // File changed on disk, etc. outside of the editor or applying undo, restore, etc.
16    async fn update_file(&self, project: ProjectId, file: FileId, text: String) -> Result<()>;
17    async fn switch_file(&self, project: ProjectId, file: FileId) -> Result<()>;
18    async fn refresh(&self, project: ProjectId) -> Result<()>;
19}
20
21#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
22#[ts(export, export_to = "FrontendApi.ts")]
23pub struct SceneGraph {
24    pub project: ProjectId,
25    pub file: FileId,
26    pub version: Version,
27
28    pub objects: Vec<Object>,
29    pub settings: Settings,
30    pub sketch_mode: Option<ObjectId>,
31}
32
33impl SceneGraph {
34    pub fn empty(project: ProjectId, file: FileId, version: Version) -> Self {
35        SceneGraph {
36            project,
37            file,
38            version,
39            objects: Vec::new(),
40            settings: Default::default(),
41            sketch_mode: None,
42        }
43    }
44}
45
46#[derive(Debug, Clone, Serialize, ts_rs::TS)]
47#[ts(export, export_to = "FrontendApi.ts")]
48pub struct SceneGraphDelta {
49    pub new_graph: SceneGraph,
50    pub new_objects: Vec<ObjectId>,
51    pub invalidates_ids: bool,
52    pub exec_outcome: ExecOutcome,
53}
54
55impl SceneGraphDelta {
56    pub fn new(
57        new_graph: SceneGraph,
58        new_objects: Vec<ObjectId>,
59        invalidates_ids: bool,
60        exec_outcome: ExecOutcome,
61    ) -> Self {
62        SceneGraphDelta {
63            new_graph,
64            new_objects,
65            invalidates_ids,
66            exec_outcome,
67        }
68    }
69}
70
71#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
72#[ts(export, export_to = "FrontendApi.ts")]
73pub struct SourceDelta {
74    pub text: String,
75}
76
77#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
78#[ts(export, export_to = "FrontendApi.ts", rename = "ApiObjectId")]
79pub struct ObjectId(pub usize);
80
81#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, ts_rs::TS)]
82#[ts(export, export_to = "FrontendApi.ts", rename = "ApiVersion")]
83pub struct Version(pub usize);
84
85#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
86#[ts(export, export_to = "FrontendApi.ts", rename = "ApiProjectId")]
87pub struct ProjectId(pub usize);
88
89#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Deserialize, Serialize, ts_rs::TS)]
90#[ts(export, export_to = "FrontendApi.ts", rename = "ApiFileId")]
91pub struct FileId(pub usize);
92
93#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
94#[ts(export, export_to = "FrontendApi.ts", rename = "ApiFile")]
95pub struct File {
96    pub id: FileId,
97    pub path: String,
98    pub text: String,
99}
100
101#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
102#[ts(export, export_to = "FrontendApi.ts", rename = "ApiObject")]
103pub struct Object {
104    pub id: ObjectId,
105    pub kind: ObjectKind,
106    pub label: String,
107    pub comments: String,
108    pub artifact_id: ArtifactId,
109    pub source: SourceRef,
110}
111
112impl Object {
113    pub fn placeholder(id: ObjectId, range: SourceRange) -> Self {
114        Object {
115            id,
116            kind: ObjectKind::Nil,
117            label: Default::default(),
118            comments: Default::default(),
119            artifact_id: ArtifactId::placeholder(),
120            source: SourceRef::Simple { range },
121        }
122    }
123}
124
125#[allow(clippy::large_enum_variant)]
126#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
127#[ts(export, export_to = "FrontendApi.ts", rename = "ApiObjectKind")]
128#[serde(tag = "type")]
129pub enum ObjectKind {
130    /// A placeholder for an object that will be solved and replaced later.
131    Nil,
132    Plane(Plane),
133    Sketch(crate::frontend::sketch::Sketch),
134    // These need to be named since the nested types are also enums. ts-rs needs
135    // a place to put the type tag.
136    Segment {
137        segment: crate::frontend::sketch::Segment,
138    },
139    Constraint {
140        constraint: crate::frontend::sketch::Constraint,
141    },
142}
143
144#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
145#[ts(export, export_to = "FrontendApi.ts", rename = "ApiPlane")]
146#[serde(rename_all = "camelCase")]
147pub enum Plane {
148    Object(ObjectId),
149    Default(PlaneName),
150}
151
152#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
153#[ts(export, export_to = "FrontendApi.ts", rename = "ApiSourceRef")]
154#[serde(tag = "type")]
155pub enum SourceRef {
156    Simple { range: SourceRange },
157    BackTrace { ranges: Vec<SourceRange> },
158}
159
160impl From<SourceRange> for SourceRef {
161    fn from(value: SourceRange) -> Self {
162        Self::Simple { range: value }
163    }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize, ts_rs::TS)]
167#[ts(export, export_to = "FrontendApi.ts")]
168pub struct Number {
169    pub value: f64,
170    pub units: NumericSuffix,
171}
172
173impl TryFrom<crate::std::args::TyF64> for Number {
174    type Error = crate::execution::types::NumericSuffixTypeConvertError;
175
176    fn try_from(value: crate::std::args::TyF64) -> std::result::Result<Self, Self::Error> {
177        Ok(Number {
178            value: value.n,
179            units: value.ty.try_into()?,
180        })
181    }
182}
183
184impl Number {
185    pub fn round(&self, digits: u8) -> Self {
186        let factor = 10f64.powi(digits as i32);
187        let rounded_value = (self.value * factor).round() / factor;
188        Number {
189            value: rounded_value,
190            units: self.units,
191        }
192    }
193}
194
195#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ts_rs::TS)]
196#[ts(export, export_to = "FrontendApi.ts")]
197#[serde(tag = "type")]
198pub enum Expr {
199    Number(Number),
200    Var(Number),
201    Variable(String),
202}
203
204#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS)]
205#[ts(export, export_to = "FrontendApi.ts")]
206pub struct Error {
207    pub msg: String,
208}
209
210impl Error {
211    pub fn file_id_in_use(id: FileId, path: &str) -> Self {
212        Error {
213            msg: format!("File ID already in use: {id:?}, currently used for `{path}`"),
214        }
215    }
216
217    pub fn bad_project(found: ProjectId, expected: Option<ProjectId>) -> Self {
218        let msg = match expected {
219            Some(expected) => format!("Project ID mismatch found: {found:?}, expected: {expected:?}"),
220            None => format!("No open project, found: {found:?}"),
221        };
222        Error { msg }
223    }
224
225    pub fn bad_version(found: Version, expected: Version) -> Self {
226        Error {
227            msg: format!("Version mismatch found: {found:?}, expected: {expected:?}"),
228        }
229    }
230
231    pub fn bad_file(found: FileId, expected: Option<FileId>) -> Self {
232        let msg = match expected {
233            Some(expected) => format!("File ID mismatch found: {found:?}, expected: {expected:?}"),
234            None => format!("File ID not found: {found:?}"),
235        };
236        Error { msg }
237    }
238
239    pub fn serialize(e: impl serde::ser::Error) -> Self {
240        Error {
241            msg: format!(
242                "Could not serialize successful KCL result. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
243            ),
244        }
245    }
246
247    pub fn deserialize(name: &str, e: impl serde::de::Error) -> Self {
248        Error {
249            msg: format!(
250                "Could not deserialize argument `{name}`. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"
251            ),
252        }
253    }
254}
255
256pub type Result<T> = std::result::Result<T, Error>;