1use std::borrow::Cow;
3use std::collections::VecDeque;
4use std::path::Path;
5use std::path::PathBuf;
6
7use indexmap::IndexMap;
8use kittycad_modeling_cmds::exec_kcl::ExecKclProjectErr;
9use kittycad_modeling_cmds::exec_kcl::ExecKclProjectOk;
10use kittycad_modeling_cmds::exec_kcl::KclFile;
11use kittycad_modeling_cmds::exec_kcl::KclProject;
12use kittycad_modeling_cmds::shared::safe_filepath::SafeFilepath;
13use kittycad_modeling_cmds::websocket::OkWebSocketResponseData;
14use kittycad_modeling_cmds::websocket::WebSocketRequest;
15
16use super::ExecOutcome;
17use super::ExecState;
18use super::ExecutorContext;
19use super::IdGenerator;
20use super::OperationsByModule;
21use crate::Program;
22use crate::SourceRange;
23use crate::TypedPath;
24use crate::engine::GridScaleBehavior;
25use crate::errors::KclError;
26use crate::errors::KclErrorDetails;
27use crate::errors::KclErrorWithOutputs;
28use crate::modules::ModuleId;
29use crate::modules::ModulePath;
30use crate::modules::ModuleSource;
31use crate::parsing::ast::types::ImportPath;
32
33impl ExecutorContext {
34 pub async fn execute_remote_project(
37 &self,
38 program: &Program,
39 state: &mut ExecState,
40 ) -> Result<ExecOutcome, KclErrorWithOutputs> {
41 let mut project = UploadedProject::collect(self, program)
42 .await
43 .map_err(KclErrorWithOutputs::no_outputs)?;
44 let id = uuid::Uuid::new_v4();
45 let range = SourceRange::synthetic();
46 let request = KclProject::new(
47 std::mem::take(&mut project.request.files),
48 project.request.entrypoint.clone(),
49 );
50 self.cache.bump_engine_scene_generation().await;
51 let engine_scene_generation = self.cache.engine_scene_generation().await;
52 let response = self
53 .engine
54 .transport
55 .inner_send_modeling_cmd(
56 id,
57 range,
58 WebSocketRequest::ExecKclProject {
59 request_id: id,
60 project: request,
61 },
62 Default::default(),
63 )
64 .await
65 .map_err(KclErrorWithOutputs::no_outputs)?;
66 let response = self
67 .engine
68 .parse_websocket_response(response, range)
69 .map_err(KclErrorWithOutputs::no_outputs)?;
70 let OkWebSocketResponseData::ExecKclProject { result } = response else {
71 return Err(KclErrorWithOutputs::no_outputs(remote_error(
72 "Engine did not return an ExecKclProject response; update the engine to support remote KCL execution.",
73 )));
74 };
75 let outcome = project.outcome(*result, engine_scene_generation)?;
76
77 state.mod_local.settings = program
78 .meta_settings()
79 .map_err(KclErrorWithOutputs::no_outputs)?
80 .unwrap_or_default();
81 state.mod_local.id_generator = IdGenerator::remote_session();
82 *self.engine.get_default_planes().write().await = outcome.default_planes.clone();
83 let grid_scale = if self.settings.fixed_size_grid {
86 GridScaleBehavior::Fixed(Some(state.length_unit()))
87 } else {
88 GridScaleBehavior::ScaleWithZoom
89 };
90 self.engine
91 .reapply_settings(
92 &self.engine_batch,
93 &self.settings,
94 range,
95 state.id_generator(),
96 grid_scale,
97 )
98 .await
99 .map_err(|err| KclErrorWithOutputs::from_error_outcome(err, outcome.clone()))?;
100 if !self.settings.geometry_only && self.settings.enable_ssao {
101 self.engine
102 .send_modeling_cmd(
103 &self.engine_batch,
104 state.next_uuid(),
105 range,
106 &kittycad_modeling_cmds::ModelingCmd::from(
107 kittycad_modeling_cmds::SetOrderIndependentTransparency::builder()
108 .enabled(false)
109 .build(),
110 ),
111 )
112 .await
113 .map_err(|err| KclErrorWithOutputs::from_error_outcome(err, outcome.clone()))?;
114 }
115 Ok(outcome)
116 }
117}
118
119fn remote_error(message: impl Into<String>) -> KclError {
120 KclError::new_semantic(KclErrorDetails::new(message.into(), vec![SourceRange::synthetic()]))
121}
122
123fn normalize(path: &Path) -> PathBuf {
125 PathBuf::from(
126 typed_path::TypedPath::derive(path.as_os_str().as_encoded_bytes())
127 .normalize()
128 .to_string_lossy()
129 .as_ref(),
130 )
131}
132
133struct UploadedProject {
134 request: KclProject,
135 files: IndexMap<String, (PathBuf, Vec<u8>)>,
137 inline: bool,
138}
139
140impl UploadedProject {
141 async fn collect(ctx: &ExecutorContext, program: &Program) -> Result<Self, KclError> {
142 let cwd = std::env::current_dir().map_err(|err| remote_error(err.to_string()))?;
143 let root = normalize(
144 &ctx.settings
145 .current_file
146 .as_ref()
147 .map(|path| cwd.join(&path.0))
148 .unwrap_or_else(|| {
149 ctx.settings
150 .project_directory
151 .as_ref()
152 .map(|dir| cwd.join(&dir.0))
153 .unwrap_or(cwd)
154 .join(format!("__zoo_kcl_{}.kcl", uuid::Uuid::new_v4()))
155 }),
156 );
157 let mut files = IndexMap::new();
158 files.insert(root.clone(), program.original_file_contents.as_bytes().to_vec());
159 let mut pending = VecDeque::from([(root.clone(), Cow::Borrowed(program), Vec::<PathBuf>::new())]);
160 while let Some((path, program, mut ancestors)) = pending.pop_front() {
161 if let Ok(real_path) = tokio::fs::canonicalize(&path).await {
162 if ancestors.contains(&real_path) {
163 continue;
164 }
165 ancestors.push(real_path);
166 }
167 let imports = std::cell::RefCell::new(Vec::new());
168 crate::walk::walk(&program.ast, |node| {
169 if let crate::walk::Node::ImportStatement(import) = node {
170 match &import.path {
171 ImportPath::Kcl { filename } => imports.borrow_mut().push((filename.clone(), true)),
172 ImportPath::Foreign { path } => imports.borrow_mut().push((path.clone(), false)),
173 ImportPath::Std { .. } => {}
174 }
175 }
176 Ok::<_, KclError>(true)
177 })
178 .map_err(|err| remote_error(err.to_string()))?;
179 for (import, is_kcl) in imports.into_inner() {
180 reject_absolute_import(&import.to_string())?;
181 let imported_path = normalize(&path.parent().unwrap_or(Path::new("")).join(&import.0));
182 if files.contains_key(&imported_path) {
183 continue;
184 }
185 let Ok(bytes) = ctx
188 .fs
189 .read(&TypedPath(imported_path.clone()), SourceRange::synthetic())
190 .await
191 else {
192 continue;
193 };
194 if is_kcl {
195 if let Ok(source) = std::str::from_utf8(&bytes)
196 && let Ok(imported) = Program::parse_no_errs(source)
197 {
198 pending.push_back((imported_path.clone(), Cow::Owned(imported), ancestors.clone()));
199 }
200 } else if imported_path
201 .extension()
202 .is_some_and(|ext| ext.eq_ignore_ascii_case("gltf") || ext.eq_ignore_ascii_case("glb"))
203 && !bytes.starts_with(b"glTF")
204 && let Ok(gltf) = gltf_json::Root::from_slice(&bytes)
205 {
206 for buffer in gltf.buffers {
207 if let Some(uri) = buffer.uri
208 && !uri.starts_with("data:")
209 {
210 reject_absolute_import(&uri)?;
211 let buffer_path = normalize(&imported_path.parent().unwrap_or(Path::new("")).join(uri));
212 if let Ok(data) = ctx
213 .fs
214 .read(&TypedPath(buffer_path.clone()), SourceRange::synthetic())
215 .await
216 {
217 files.entry(buffer_path).or_insert(data);
218 }
219 }
220 }
221 }
222 files.insert(imported_path, bytes);
223 }
224 }
225
226 let mut directory = root.parent().unwrap_or(Path::new("")).to_path_buf();
228 for path in files.keys() {
229 while !path.starts_with(&directory) {
230 if !directory.pop() {
231 return Err(remote_error("Imported files do not share a project root"));
232 }
233 }
234 }
235 let relative = |path: &Path| -> Result<String, KclError> {
236 path.strip_prefix(&directory)
237 .map(|path| path.to_string_lossy().replace('\\', "/"))
238 .map_err(|err| remote_error(err.to_string()))
239 };
240 let entrypoint = safe_path(&relative(&root)?)?;
241 let files: IndexMap<_, _> = files
242 .into_iter()
243 .map(|(path, bytes)| Ok((relative(&path)?, (path, bytes))))
244 .collect::<Result<_, KclError>>()?;
245 let request = KclProject::new(
246 files
247 .iter()
248 .map(|(name, (_, bytes))| Ok(KclFile::new(safe_path(name)?, bytes.clone())))
249 .collect::<Result<_, KclError>>()?,
250 entrypoint,
251 );
252 Ok(Self {
253 request,
254 files,
255 inline: ctx.settings.current_file.is_none(),
256 })
257 }
258
259 fn sources(&self, filenames: &mut IndexMap<ModuleId, ModulePath>) -> IndexMap<ModuleId, ModuleSource> {
260 let mut sources = IndexMap::new();
261 for (id, path) in filenames {
262 let source = if id.is_top_level() {
263 let (original, bytes) = &self.files[&self.request.entrypoint.to_string()];
264 *path = if self.inline {
265 ModulePath::Main
266 } else {
267 ModulePath::Local {
268 value: TypedPath(original.clone()),
269 original_import_path: None,
270 }
271 };
272 Some(bytes.as_slice())
273 } else {
274 match path {
275 ModulePath::Main => Some(self.files[&self.request.entrypoint.to_string()].1.as_slice()),
276 ModulePath::Local { value, .. } => {
277 let name = normalize(&value.0).to_string_lossy().replace('\\', "/");
278 self.files.get(&name).map(|(original, bytes)| {
279 *value = TypedPath(original.clone());
280 bytes.as_slice()
281 })
282 }
283 ModulePath::Std { value } => crate::modules::read_std(value).map(str::as_bytes),
284 }
285 };
286 if let Some(source) = source.and_then(|source| std::str::from_utf8(source).ok()) {
287 sources.insert(
288 *id,
289 ModuleSource {
290 path: path.clone(),
291 source: source.to_owned(),
292 },
293 );
294 }
295 }
296 sources
297 }
298
299 fn outcome(
300 &self,
301 result: Result<ExecKclProjectOk, ExecKclProjectErr>,
302 engine_scene_generation: usize,
303 ) -> Result<ExecOutcome, KclErrorWithOutputs> {
304 let mut response = match result {
305 Ok(response) => response,
306 Err(response) => {
307 if let Some(mut error) = response.error {
308 self.restore_entrypoint_name(&mut error.details_mut().message);
309 let mut error = KclErrorWithOutputs::no_outputs(error);
310 error.non_fatal = response.non_fatal;
311 for issue in &mut error.non_fatal {
312 self.restore_entrypoint_name(&mut issue.message);
313 }
314 error.variables = response.variables;
315 error.operations = OperationsByModule {
316 map: response.operations,
317 };
318 error.artifact_graph = response.artifact_graph;
319 error.scene_objects = response.scene_objects;
320 error.filenames = response.filenames;
321 error.source_files = self.sources(&mut error.filenames);
322 error.default_planes = response.default_planes.map(|planes| *planes);
323 error.engine_scene_generation = Some(engine_scene_generation);
324 return Err(error);
325 }
326 ExecKclProjectOk::builder()
329 .variables(response.variables)
330 .operations(response.operations)
331 .artifact_graph(response.artifact_graph)
332 .scene_objects(response.scene_objects)
333 .filenames(response.filenames)
334 .issues(response.non_fatal)
335 .maybe_default_planes(response.default_planes)
336 .build()
337 }
338 };
339 for issue in &mut response.issues {
340 self.restore_entrypoint_name(&mut issue.message);
341 }
342 let mut filenames = response.filenames;
343 let source_files = self.sources(&mut filenames);
344 Ok(ExecOutcome {
345 variables: response.variables,
346 operations: OperationsByModule {
347 map: response.operations,
348 },
349 artifact_graph: response.artifact_graph,
350 scene_objects: response.scene_objects,
351 source_range_to_object: Default::default(),
352 var_solutions: Default::default(),
353 refactor_metadata: Default::default(),
354 issues: response.issues,
355 filenames,
356 engine_scene_generation,
357 source_files,
358 default_planes: response.default_planes.map(|planes| *planes),
359 #[cfg(test)]
360 test_program_memory: Default::default(),
361 })
362 }
363
364 fn restore_entrypoint_name(&self, message: &mut String) {
365 let upload_name = self.request.entrypoint.to_string();
366 let client_name = if self.inline {
367 Cow::Borrowed("main")
368 } else {
369 self.files[&upload_name].0.to_string_lossy()
370 };
371 *message = message.replace(&format!("`{upload_name}`"), &format!("`{client_name}`"));
372 }
373}
374
375fn reject_absolute_import(path: &str) -> Result<(), KclError> {
376 if typed_path::UnixPath::new(path).is_absolute() || typed_path::WindowsPath::new(path).has_root() {
377 return Err(remote_error(format!(
378 "Absolute import paths are not supported by engine execution: {path}"
379 )));
380 }
381 Ok(())
382}
383
384fn safe_path(path: &str) -> Result<SafeFilepath, KclError> {
385 path.parse()
386 .map_err(|err| remote_error(format!("Cannot upload project path {path}: {err}")))
387}
388
389#[cfg(test)]
390mod tests {
391 use std::sync::Arc;
392
393 use kittycad_modeling_cmds::websocket::WebSocketResponse;
394
395 use super::*;
396 use crate::ExecutorSettings;
397 use crate::engine_connection::EngineManager;
398 use crate::engine_connection::EngineTransport;
399
400 const ROOT: &str = "@settings(kclVersion = 3.0)\nimport \"sub/main.kcl\"\nvalue = 1\n";
401
402 async fn project() -> (tempfile::TempDir, UploadedProject) {
403 let dir = tempfile::tempdir().unwrap();
404 std::fs::create_dir(dir.path().join("sub")).unwrap();
405 std::fs::write(
406 dir.path().join("sub/main.kcl"),
407 "@settings(kclVersion = 3.0)\nimport \"main.kcl\"\nexport value = 2\n",
408 )
409 .unwrap();
410 std::fs::write(dir.path().join("main.kcl"), ROOT).unwrap();
411 std::fs::write(dir.path().join("unrelated.txt"), "must not upload").unwrap();
412 let ctx = ExecutorContext::new_mock(Some(ExecutorSettings {
413 current_file: Some(TypedPath(dir.path().join("main.kcl"))),
414 ..Default::default()
415 }))
416 .await;
417 let uploaded = UploadedProject::collect(&ctx, &Program::parse_no_errs(ROOT).unwrap())
418 .await
419 .unwrap();
420 (dir, uploaded)
421 }
422
423 fn response() -> ExecKclProjectOk {
424 ExecKclProjectOk::builder()
425 .artifact_graph(Default::default())
426 .variables(Default::default())
427 .operations(Default::default())
428 .issues(Vec::new())
429 .scene_objects(Vec::new())
430 .filenames(IndexMap::from([
431 (
432 ModuleId::default(),
433 ModulePath::Local {
434 value: "main.kcl".into(),
435 original_import_path: None,
436 },
437 ),
438 (
439 ModuleId::from_usize(1),
440 ModulePath::Local {
441 value: "sub/main.kcl".into(),
442 original_import_path: Some("sub/main.kcl".into()),
443 },
444 ),
445 ]))
446 .build()
447 }
448
449 #[tokio::test]
450 async fn uploads_only_dependencies_and_stops_at_cycles() {
451 let (_, project) = project().await;
452 assert_eq!(project.request.entrypoint.to_string(), "main.kcl");
453 assert_eq!(
454 project.files.keys().map(String::as_str).collect::<Vec<_>>(),
455 ["main.kcl", "sub/main.kcl"]
456 );
457 }
458
459 #[tokio::test]
460 async fn reconstructs_sources_from_uploaded_bytes_after_files_change() {
461 let (dir, project) = project().await;
462 std::fs::write(dir.path().join("main.kcl"), "changed").unwrap();
463 let outcome = project.outcome(Ok(response()), 1).unwrap();
464 assert_eq!(outcome.source_files[&ModuleId::default()].source, ROOT);
465 assert_eq!(
466 outcome.filenames[&ModuleId::default()].to_string(),
467 dir.path().join("main.kcl").display().to_string()
468 );
469 assert_eq!(outcome.source_files.len(), 2);
470 }
471
472 #[tokio::test]
473 async fn diagnostics_keep_the_client_entrypoint_name() {
474 let ctx = ExecutorContext::new_mock(None).await;
475 let program = Program::parse_no_errs("@settings(kclVersion = 3.0)\nvalue = 1").unwrap();
476 let project = UploadedProject::collect(&ctx, &program).await.unwrap();
477 let error = project
478 .outcome(
479 Err(ExecKclProjectErr::error_before_execution(remote_error(format!(
480 "The entry point `{}` failed",
481 project.request.entrypoint
482 )))),
483 1,
484 )
485 .unwrap_err();
486 assert_eq!(error.error.message(), "The entry point `main` failed");
487 }
488
489 #[tokio::test]
490 async fn retains_partial_output_on_fatal_errors_and_all_nonfatal_issues() {
491 let (_, project) = project().await;
492 let success = response();
493 let mut warning = crate::CompilationIssue::err(SourceRange::synthetic(), "keep warning");
494 warning.severity = crate::errors::Severity::Warning;
495 let error = project
496 .outcome(
497 Err(ExecKclProjectErr::builder()
498 .error(remote_error("failed"))
499 .non_fatal(vec![warning.clone()])
500 .variables(success.variables)
501 .operations(success.operations)
502 .artifact_graph(success.artifact_graph)
503 .scene_objects(success.scene_objects)
504 .filenames(success.filenames)
505 .build()),
506 1,
507 )
508 .unwrap_err();
509 assert_eq!(error.non_fatal, [warning.clone()]);
510 assert_eq!(error.engine_scene_generation, Some(1));
511 assert_eq!(error.source_files.len(), 2);
512 let response = response();
513 let outcome = project
514 .outcome(
515 Err(ExecKclProjectErr::builder()
516 .non_fatal(vec![warning.clone()])
517 .variables(response.variables)
518 .operations(response.operations)
519 .artifact_graph(response.artifact_graph)
520 .scene_objects(response.scene_objects)
521 .filenames(response.filenames)
522 .build()),
523 1,
524 )
525 .unwrap();
526 assert_eq!(outcome.issues, [warning]);
527 assert_eq!(outcome.engine_scene_generation, 1);
528 }
529
530 #[test]
531 fn absolute_imports_are_rejected_on_all_platforms() {
532 for path in ["/tmp/part.kcl", "C:\\project\\part.kcl", "\\\\server\\part.kcl"] {
533 assert!(reject_absolute_import(path).is_err(), "{path}");
534 }
535 reject_absolute_import("../part.kcl").unwrap();
536 }
537
538 #[tokio::test]
539 async fn includes_gltf_buffers_without_interpreting_them_as_modules() {
540 let dir = tempfile::tempdir().unwrap();
541 let source = "@settings(kclVersion = 3.0)\nimport \"shape.gltf\" as shape\n";
542 std::fs::write(
543 dir.path().join("shape.gltf"),
544 r#"{"asset":{"version":"2.0"},"buffers":[{"uri":"buffer.bin","byteLength":4}]}"#,
545 )
546 .unwrap();
547 std::fs::write(dir.path().join("buffer.bin"), [0, 255, 1, 2]).unwrap();
548 let ctx = ExecutorContext::new_mock(Some(ExecutorSettings {
549 current_file: Some(TypedPath(dir.path().join("main.kcl"))),
550 ..Default::default()
551 }))
552 .await;
553 let project = UploadedProject::collect(&ctx, &Program::parse_no_errs(source).unwrap())
554 .await
555 .unwrap();
556 assert_eq!(project.files["buffer.bin"].1, [0, 255, 1, 2]);
557 }
558
559 #[tokio::test]
560 async fn uploaded_nested_entrypoints_resolve_parent_relative_assets() {
561 let dir = tempfile::tempdir().unwrap();
562 std::fs::create_dir(dir.path().join("sub")).unwrap();
563 let code = "@settings(kclVersion = 3.0)\nimport \"../part.step\" as part\n";
564 std::fs::write(dir.path().join("part.step"), b"step file sent to the engine").unwrap();
565 let client = ExecutorContext::new_mock(Some(ExecutorSettings {
566 current_file: Some(TypedPath(dir.path().join("sub/main.kcl"))),
567 ..Default::default()
568 }))
569 .await;
570 let program = Program::parse_no_errs(code).unwrap();
571 let project = UploadedProject::collect(&client, &program).await.unwrap().request;
572 let entrypoint = TypedPath::new(&project.entrypoint.to_string());
573 let files = crate::InMemoryFiles::new(
574 project
575 .files
576 .into_iter()
577 .map(|file| (file.path.to_string(), file.contents))
578 .collect(),
579 );
580 let ctx = ExecutorContext::new_with_engine_and_fs(
583 Arc::new(EngineManager::new_mock()),
584 crate::new_file_system_handle(files),
585 ExecutorSettings {
586 project_directory: entrypoint.parent(),
587 current_file: Some(entrypoint),
588 geometry_only: true,
589 ..Default::default()
590 },
591 );
592 let mut state = ExecState::new(&ctx);
593 ctx.run(&program, &mut state).await.unwrap();
594 }
595
596 struct RecordingTransport {
597 requests: Arc<tokio::sync::Mutex<Vec<WebSocketRequest>>>,
598 }
599
600 #[async_trait::async_trait]
601 impl EngineTransport for RecordingTransport {
602 async fn inner_fire_modeling_cmd(
603 &self,
604 _: uuid::Uuid,
605 _: SourceRange,
606 _: WebSocketRequest,
607 _: std::collections::HashMap<uuid::Uuid, SourceRange>,
608 ) -> Result<(), KclError> {
609 panic!("unexpected unawaited command")
610 }
611 async fn inner_send_modeling_cmd(
612 &self,
613 id: uuid::Uuid,
614 _: SourceRange,
615 request: WebSocketRequest,
616 _: std::collections::HashMap<uuid::Uuid, SourceRange>,
617 ) -> Result<WebSocketResponse, KclError> {
618 let response = if matches!(request, WebSocketRequest::ExecKclProject { .. }) {
619 OkWebSocketResponseData::ExecKclProject {
620 result: Box::new(Ok(response())),
621 }
622 } else {
623 OkWebSocketResponseData::Modeling {
624 modeling_response: kittycad_modeling_cmds::ok_response::OkModelingCmdResponse::Empty {},
625 }
626 };
627 self.requests.lock().await.push(request);
628 Ok(WebSocketResponse::success(Some(id), response))
629 }
630 async fn close(&self) -> Result<(), crate::engine_connection::TransportCloseError> {
631 Ok(())
632 }
633 }
634
635 #[tokio::test]
636 async fn executes_once_and_keeps_the_transport_for_followups() {
637 let requests = Arc::new(tokio::sync::Mutex::new(Vec::new()));
638 let mut engine = EngineManager::new_mock();
639 engine.transport = Arc::new(Box::new(RecordingTransport {
640 requests: requests.clone(),
641 }));
642 let ctx = ExecutorContext::new_with_engine(
643 Arc::new(engine),
644 ExecutorSettings {
645 geometry_only: true,
646 ..Default::default()
647 },
648 );
649 let mut state = ExecState::new(&ctx);
650 let outcome = ctx
651 .execute_remote_project(
652 &Program::parse_no_errs("@settings(kclVersion = 3.0)\nvalue = 1").unwrap(),
653 &mut state,
654 )
655 .await
656 .unwrap();
657 ctx.engine
658 .send_modeling_cmd(
659 &ctx.engine_batch,
660 state.next_uuid(),
661 SourceRange::synthetic(),
662 &kittycad_modeling_cmds::ModelingCmd::ViewIsometric(
663 kittycad_modeling_cmds::ViewIsometric::builder().padding(0.1).build(),
664 ),
665 )
666 .await
667 .unwrap();
668 assert_eq!(outcome.engine_scene_generation, 1);
669 assert_eq!(ctx.cache.engine_scene_generation().await, 1);
670 let requests = requests.lock().await;
671 assert_eq!(requests.len(), 2);
672 assert!(matches!(requests[0], WebSocketRequest::ExecKclProject { .. }));
673 assert!(matches!(requests[1], WebSocketRequest::ModelingCmdReq(_)));
674 assert_ne!(state.next_uuid(), IdGenerator::new(None).next_uuid());
675 }
676}