1use crate::{
7 VeracodeClient, VeracodeError,
8 app::{Application, BusinessCriticality},
9 build::{Build, BuildError},
10 sandbox::{Sandbox, SandboxError},
11 scan::ScanError,
12};
13
14pub struct VeracodeWorkflow {
16 client: VeracodeClient,
17}
18
19pub type WorkflowResult<T> = Result<T, WorkflowError>;
21
22#[derive(Debug)]
24pub enum WorkflowError {
25 Api(VeracodeError),
27 Sandbox(SandboxError),
29 Scan(ScanError),
31 Build(BuildError),
33 Workflow(String),
35 AccessDenied(String),
37 NotFound(String),
39}
40
41impl std::fmt::Display for WorkflowError {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 WorkflowError::Api(err) => write!(f, "API error: {err}"),
45 WorkflowError::Sandbox(err) => write!(f, "Sandbox error: {err}"),
46 WorkflowError::Scan(err) => write!(f, "Scan error: {err}"),
47 WorkflowError::Build(err) => write!(f, "Build error: {err}"),
48 WorkflowError::Workflow(msg) => write!(f, "Workflow error: {msg}"),
49 WorkflowError::AccessDenied(msg) => write!(f, "Access denied: {msg}"),
50 WorkflowError::NotFound(msg) => write!(f, "Not found: {msg}"),
51 }
52 }
53}
54
55impl std::error::Error for WorkflowError {}
56
57impl From<VeracodeError> for WorkflowError {
58 fn from(err: VeracodeError) -> Self {
59 WorkflowError::Api(err)
60 }
61}
62
63impl From<SandboxError> for WorkflowError {
64 fn from(err: SandboxError) -> Self {
65 WorkflowError::Sandbox(err)
66 }
67}
68
69impl From<ScanError> for WorkflowError {
70 fn from(err: ScanError) -> Self {
71 WorkflowError::Scan(err)
72 }
73}
74
75impl From<BuildError> for WorkflowError {
76 fn from(err: BuildError) -> Self {
77 WorkflowError::Build(err)
78 }
79}
80
81#[derive(Debug, Clone)]
83pub struct WorkflowConfig {
84 pub app_name: String,
86 pub sandbox_name: String,
88 pub business_criticality: BusinessCriticality,
90 pub app_description: Option<String>,
92 pub sandbox_description: Option<String>,
94 pub file_paths: Vec<String>,
96 pub auto_scan: bool,
98 pub scan_all_modules: bool,
100}
101
102impl WorkflowConfig {
103 pub fn new(app_name: String, sandbox_name: String) -> Self {
105 Self {
106 app_name,
107 sandbox_name,
108 business_criticality: BusinessCriticality::Medium,
109 app_description: None,
110 sandbox_description: None,
111 file_paths: Vec::new(),
112 auto_scan: true,
113 scan_all_modules: true,
114 }
115 }
116
117 pub fn with_business_criticality(mut self, criticality: BusinessCriticality) -> Self {
119 self.business_criticality = criticality;
120 self
121 }
122
123 pub fn with_app_description(mut self, description: String) -> Self {
125 self.app_description = Some(description);
126 self
127 }
128
129 pub fn with_sandbox_description(mut self, description: String) -> Self {
131 self.sandbox_description = Some(description);
132 self
133 }
134
135 pub fn with_file(mut self, file_path: String) -> Self {
137 self.file_paths.push(file_path);
138 self
139 }
140
141 pub fn with_files(mut self, file_paths: Vec<String>) -> Self {
143 self.file_paths.extend(file_paths);
144 self
145 }
146
147 pub fn with_auto_scan(mut self, auto_scan: bool) -> Self {
149 self.auto_scan = auto_scan;
150 self
151 }
152
153 pub fn with_scan_all_modules(mut self, scan_all: bool) -> Self {
155 self.scan_all_modules = scan_all;
156 self
157 }
158}
159
160#[derive(Debug, Clone)]
162pub struct WorkflowResultData {
163 pub application: Application,
165 pub sandbox: Sandbox,
167 pub app_id: String,
169 pub sandbox_id: String,
171 pub build_id: Option<String>,
173 pub app_created: bool,
175 pub sandbox_created: bool,
177 pub files_uploaded: usize,
179}
180
181impl VeracodeWorkflow {
182 pub fn new(client: VeracodeClient) -> Self {
184 Self { client }
185 }
186
187 pub async fn execute_complete_workflow(
205 &self,
206 config: WorkflowConfig,
207 ) -> WorkflowResult<WorkflowResultData> {
208 println!("๐ Starting complete Veracode XML API workflow");
209 println!(" Application: {}", config.app_name);
210 println!(" Sandbox: {}", config.sandbox_name);
211 println!(" Files to upload: {}", config.file_paths.len());
212
213 println!("\n๐ฑ Step 1: Checking application existence...");
215 let (application, app_created) =
216 match self.client.get_application_by_name(&config.app_name).await {
217 Ok(Some(app)) => {
218 println!(
219 " โ
Application '{}' found (GUID: {})",
220 config.app_name, app.guid
221 );
222 (app, false)
223 }
224 Ok(None) => {
225 println!(
226 " โ Application '{}' not found, creating...",
227 config.app_name
228 );
229 match self
230 .client
231 .create_application_if_not_exists(
232 &config.app_name,
233 config.business_criticality,
234 config.app_description.clone(),
235 None, )
237 .await
238 {
239 Ok(app) => {
240 println!(
241 " โ
Application '{}' created successfully (GUID: {})",
242 config.app_name, app.guid
243 );
244 (app, true)
245 }
246 Err(VeracodeError::InvalidResponse(msg))
247 if msg.contains("403") || msg.contains("401") =>
248 {
249 return Err(WorkflowError::AccessDenied(format!(
250 "Access denied creating application '{}': {}",
251 config.app_name, msg
252 )));
253 }
254 Err(e) => return Err(WorkflowError::Api(e)),
255 }
256 }
257 Err(VeracodeError::InvalidResponse(msg))
258 if msg.contains("403") || msg.contains("401") =>
259 {
260 return Err(WorkflowError::AccessDenied(format!(
261 "Access denied checking application '{}': {}",
262 config.app_name, msg
263 )));
264 }
265 Err(e) => return Err(WorkflowError::Api(e)),
266 };
267
268 let app_id = self.client.get_app_id_from_guid(&application.guid).await?;
270 println!(" ๐ Application ID for XML API: {app_id}");
271
272 println!("\n๐งช Step 2: Checking sandbox existence...");
274 let sandbox_api = self.client.sandbox_api();
275 let (sandbox, sandbox_created) = match sandbox_api
276 .get_sandbox_by_name(&application.guid, &config.sandbox_name)
277 .await
278 {
279 Ok(Some(sandbox)) => {
280 println!(
281 " โ
Sandbox '{}' found (GUID: {})",
282 config.sandbox_name, sandbox.guid
283 );
284 (sandbox, false)
285 }
286 Ok(None) => {
287 println!(
288 " โ Sandbox '{}' not found, creating...",
289 config.sandbox_name
290 );
291 match sandbox_api
292 .create_sandbox_if_not_exists(
293 &application.guid,
294 &config.sandbox_name,
295 config.sandbox_description.clone(),
296 )
297 .await
298 {
299 Ok(sandbox) => {
300 println!(
301 " โ
Sandbox '{}' created successfully (GUID: {})",
302 config.sandbox_name, sandbox.guid
303 );
304 (sandbox, true)
305 }
306 Err(SandboxError::Api(VeracodeError::InvalidResponse(msg)))
307 if msg.contains("403") || msg.contains("401") =>
308 {
309 return Err(WorkflowError::AccessDenied(format!(
310 "Access denied creating sandbox '{}': {}",
311 config.sandbox_name, msg
312 )));
313 }
314 Err(e) => return Err(WorkflowError::Sandbox(e)),
315 }
316 }
317 Err(SandboxError::Api(VeracodeError::InvalidResponse(msg)))
318 if msg.contains("403") || msg.contains("401") =>
319 {
320 return Err(WorkflowError::AccessDenied(format!(
321 "Access denied checking sandbox '{}': {}",
322 config.sandbox_name, msg
323 )));
324 }
325 Err(e) => return Err(WorkflowError::Sandbox(e)),
326 };
327
328 let sandbox_id = sandbox_api
330 .get_sandbox_id_from_guid(&application.guid, &sandbox.guid)
331 .await?;
332 println!(" ๐ Sandbox ID for XML API: {sandbox_id}");
333
334 println!("\n๐ค Step 3: Uploading files to sandbox...");
336 let scan_api = self.client.scan_api();
337 let mut files_uploaded = 0;
338
339 for file_path in &config.file_paths {
340 println!(" ๐ Uploading file: {file_path}");
341 match scan_api
342 .upload_file_to_sandbox(&app_id, file_path, &sandbox_id)
343 .await
344 {
345 Ok(uploaded_file) => {
346 println!(
347 " โ
File uploaded successfully: {} (ID: {})",
348 uploaded_file.file_name, uploaded_file.file_id
349 );
350 files_uploaded += 1;
351 }
352 Err(ScanError::FileNotFound(_)) => {
353 return Err(WorkflowError::NotFound(format!(
354 "File not found: {file_path}"
355 )));
356 }
357 Err(ScanError::Unauthorized) => {
358 return Err(WorkflowError::AccessDenied(format!(
359 "Access denied uploading file: {file_path}"
360 )));
361 }
362 Err(ScanError::PermissionDenied) => {
363 return Err(WorkflowError::AccessDenied(format!(
364 "Permission denied uploading file: {file_path}"
365 )));
366 }
367 Err(e) => return Err(WorkflowError::Scan(e)),
368 }
369 }
370
371 println!(" ๐ Total files uploaded: {files_uploaded}");
372
373 let build_id = if config.auto_scan {
375 println!("\n๐ Step 4: Starting prescan and scan...");
376 match scan_api
377 .upload_and_scan_sandbox(&app_id, &sandbox_id, &config.file_paths[0])
378 .await
379 {
380 Ok(build_id) => {
381 println!(" โ
Scan started successfully with build ID: {build_id}");
382 Some(build_id)
383 }
384 Err(ScanError::Unauthorized) => {
385 return Err(WorkflowError::AccessDenied(
386 "Access denied starting scan".to_string(),
387 ));
388 }
389 Err(ScanError::PermissionDenied) => {
390 return Err(WorkflowError::AccessDenied(
391 "Permission denied starting scan".to_string(),
392 ));
393 }
394 Err(e) => {
395 println!(" โ ๏ธ Warning: Could not start scan automatically: {e}");
396 println!(
397 " ๐ก You may need to start the scan manually from the Veracode platform"
398 );
399 None
400 }
401 }
402 } else {
403 println!("\nโญ๏ธ Step 4: Skipping automatic scan (auto_scan = false)");
404 None
405 };
406
407 let result = WorkflowResultData {
408 application,
409 sandbox,
410 app_id,
411 sandbox_id,
412 build_id: build_id.clone(),
413 app_created,
414 sandbox_created,
415 files_uploaded,
416 };
417
418 println!("\nโ
Workflow completed successfully!");
419 println!(" ๐ Summary:");
420 println!(
421 " - Application: {} (created: {})",
422 config.app_name, app_created
423 );
424 println!(
425 " - Sandbox: {} (created: {})",
426 config.sandbox_name, sandbox_created
427 );
428 println!(" - Files uploaded: {files_uploaded}");
429 if let Some(build_id) = &build_id {
430 println!(
431 " - Scan started: {} (build ID: {})",
432 config.auto_scan, build_id
433 );
434 } else {
435 println!(" - Scan started: {}", config.auto_scan);
436 }
437
438 Ok(result)
439 }
440
441 pub async fn ensure_app_and_sandbox(
456 &self,
457 app_name: &str,
458 sandbox_name: &str,
459 business_criticality: BusinessCriticality,
460 ) -> WorkflowResult<(Application, Sandbox, String, String)> {
461 let config = WorkflowConfig::new(app_name.to_string(), sandbox_name.to_string())
462 .with_business_criticality(business_criticality)
463 .with_auto_scan(false);
464
465 let result = self.execute_complete_workflow(config).await?;
466 Ok((
467 result.application,
468 result.sandbox,
469 result.app_id,
470 result.sandbox_id,
471 ))
472 }
473
474 pub async fn get_application_by_name(&self, app_name: &str) -> WorkflowResult<Application> {
484 match self.client.get_application_by_name(app_name).await? {
485 Some(app) => Ok(app),
486 None => Err(WorkflowError::NotFound(format!(
487 "Application '{app_name}' not found"
488 ))),
489 }
490 }
491
492 pub async fn get_sandbox_by_name(
503 &self,
504 app_guid: &str,
505 sandbox_name: &str,
506 ) -> WorkflowResult<Sandbox> {
507 let sandbox_api = self.client.sandbox_api();
508 match sandbox_api
509 .get_sandbox_by_name(app_guid, sandbox_name)
510 .await?
511 {
512 Some(sandbox) => Ok(sandbox),
513 None => Err(WorkflowError::NotFound(format!(
514 "Sandbox '{sandbox_name}' not found"
515 ))),
516 }
517 }
518
519 pub async fn delete_sandbox_builds(
532 &self,
533 app_name: &str,
534 sandbox_name: &str,
535 ) -> WorkflowResult<()> {
536 println!("๐๏ธ Deleting builds from sandbox '{sandbox_name}'...");
537
538 let app = self.get_application_by_name(app_name).await?;
540 let sandbox = self.get_sandbox_by_name(&app.guid, sandbox_name).await?;
541
542 let app_id = self.client.get_app_id_from_guid(&app.guid).await?;
544 let sandbox_api = self.client.sandbox_api();
545 let sandbox_id = sandbox_api
546 .get_sandbox_id_from_guid(&app.guid, &sandbox.guid)
547 .await?;
548
549 let scan_api = self.client.scan_api();
551 match scan_api
552 .delete_all_sandbox_builds(&app_id, &sandbox_id)
553 .await
554 {
555 Ok(_) => {
556 println!(" โ
Successfully deleted all builds from sandbox '{sandbox_name}'");
557 Ok(())
558 }
559 Err(ScanError::Unauthorized) => Err(WorkflowError::AccessDenied(
560 "Access denied deleting sandbox builds".to_string(),
561 )),
562 Err(ScanError::PermissionDenied) => Err(WorkflowError::AccessDenied(
563 "Permission denied deleting sandbox builds".to_string(),
564 )),
565 Err(ScanError::BuildNotFound) => {
566 println!(" โน๏ธ No builds found to delete in sandbox '{sandbox_name}'");
567 Ok(())
568 }
569 Err(e) => Err(WorkflowError::Scan(e)),
570 }
571 }
572
573 pub async fn delete_sandbox(&self, app_name: &str, sandbox_name: &str) -> WorkflowResult<()> {
586 println!("๐๏ธ Deleting sandbox '{sandbox_name}'...");
587
588 let app = self.get_application_by_name(app_name).await?;
590 let sandbox = self.get_sandbox_by_name(&app.guid, sandbox_name).await?;
591
592 let _ = self.delete_sandbox_builds(app_name, sandbox_name).await;
594
595 let sandbox_api = self.client.sandbox_api();
597 match sandbox_api.delete_sandbox(&app.guid, &sandbox.guid).await {
598 Ok(_) => {
599 println!(" โ
Successfully deleted sandbox '{sandbox_name}'");
600 Ok(())
601 }
602 Err(SandboxError::Api(VeracodeError::InvalidResponse(msg)))
603 if msg.contains("403") || msg.contains("401") =>
604 {
605 Err(WorkflowError::AccessDenied(format!(
606 "Access denied deleting sandbox '{sandbox_name}': {msg}"
607 )))
608 }
609 Err(SandboxError::NotFound) => {
610 println!(
611 " โน๏ธ Sandbox '{sandbox_name}' not found (may have been already deleted)"
612 );
613 Ok(())
614 }
615 Err(e) => Err(WorkflowError::Sandbox(e)),
616 }
617 }
618
619 pub async fn delete_application(&self, app_name: &str) -> WorkflowResult<()> {
632 println!("๐๏ธ Deleting application '{app_name}'...");
633
634 let app = self.get_application_by_name(app_name).await?;
636
637 let sandbox_api = self.client.sandbox_api();
639 match sandbox_api.list_sandboxes(&app.guid, None).await {
640 Ok(sandboxes) => {
641 for sandbox in sandboxes {
642 println!(" ๐๏ธ Deleting sandbox: {}", sandbox.name);
643 let _ = self.delete_sandbox(app_name, &sandbox.name).await;
644 }
645 }
646 Err(e) => {
647 println!(" โ ๏ธ Warning: Could not list sandboxes for cleanup: {e}");
648 }
649 }
650
651 let app_id = self.client.get_app_id_from_guid(&app.guid).await?;
653 let scan_api = self.client.scan_api();
654 match scan_api.delete_all_app_builds(&app_id).await {
655 Ok(_) => println!(" โ
Deleted all application builds"),
656 Err(e) => println!(" โ ๏ธ Warning: Could not delete application builds: {e}"),
657 }
658
659 match self.client.delete_application(&app.guid).await {
661 Ok(_) => {
662 println!(" โ
Successfully deleted application '{app_name}'");
663 Ok(())
664 }
665 Err(VeracodeError::InvalidResponse(msg))
666 if msg.contains("403") || msg.contains("401") =>
667 {
668 Err(WorkflowError::AccessDenied(format!(
669 "Access denied deleting application '{app_name}': {msg}"
670 )))
671 }
672 Err(VeracodeError::NotFound(_)) => {
673 println!(
674 " โน๏ธ Application '{app_name}' not found (may have been already deleted)"
675 );
676 Ok(())
677 }
678 Err(e) => Err(WorkflowError::Api(e)),
679 }
680 }
681
682 pub async fn complete_cleanup(&self, app_name: &str) -> WorkflowResult<()> {
698 println!("๐งน Starting complete cleanup for application '{app_name}'");
699 println!(" โ ๏ธ WARNING: This will delete ALL data associated with this application");
700 println!(" This includes all sandboxes, builds, and scan results");
701
702 match self.delete_application(app_name).await {
703 Ok(_) => {
704 println!("โ
Complete cleanup finished successfully");
705 Ok(())
706 }
707 Err(WorkflowError::NotFound(_)) => {
708 println!("โน๏ธ Application '{app_name}' not found - nothing to clean up");
709 Ok(())
710 }
711 Err(e) => {
712 println!("โ Cleanup encountered errors: {e}");
713 Err(e)
714 }
715 }
716 }
717
718 pub async fn ensure_build_exists(
733 &self,
734 app_id: &str,
735 sandbox_id: Option<&str>,
736 version: Option<&str>,
737 ) -> WorkflowResult<Build> {
738 self.ensure_build_exists_with_policy(app_id, sandbox_id, version, 1)
739 .await
740 }
741
742 pub async fn ensure_build_exists_with_policy(
760 &self,
761 app_id: &str,
762 sandbox_id: Option<&str>,
763 version: Option<&str>,
764 deletion_policy: u8,
765 ) -> WorkflowResult<Build> {
766 println!("๐ Checking if build exists (deletion policy: {deletion_policy})...");
767
768 let build_api = self.client.build_api();
769
770 let get_request = crate::build::GetBuildInfoRequest {
772 app_id: app_id.to_string(),
773 build_id: None, sandbox_id: sandbox_id.map(|s| s.to_string()),
775 };
776
777 match build_api.get_build_info(get_request).await {
778 Ok(build) => {
779 println!(" ๐ Build already exists: {}", build.build_id);
780 if let Some(build_version) = &build.version {
781 println!(" Existing Version: {build_version}");
782 }
783
784 let build_status_str = build
786 .attributes
787 .get("status")
788 .or_else(|| build.attributes.get("analysis_status"))
789 .or_else(|| build.attributes.get("scan_status"))
790 .map(|s| s.as_str())
791 .unwrap_or("Unknown");
792
793 let build_status = crate::build::BuildStatus::from_string(build_status_str);
794 println!(" Build Status: {build_status}");
795
796 if deletion_policy == 0 {
798 return Err(WorkflowError::Workflow(format!(
799 "Build {} already exists and deletion policy is set to 'Never delete' (0). Cannot proceed with upload.",
800 build.build_id
801 )));
802 }
803
804 if build_status == crate::build::BuildStatus::ResultsReady {
806 println!(
807 " ๐ Build has 'Results Ready' status - creating new build to preserve existing results"
808 );
809 self.create_build_for_upload(app_id, sandbox_id, version)
810 .await
811 }
812 else if build_status.is_safe_to_delete(deletion_policy) {
814 println!(
815 " ๐๏ธ Build is safe to delete according to policy {deletion_policy}. Deleting..."
816 );
817
818 let delete_request = crate::build::DeleteBuildRequest {
820 app_id: app_id.to_string(),
821 sandbox_id: sandbox_id.map(|s| s.to_string()),
822 };
823
824 match build_api.delete_build(delete_request).await {
825 Ok(_) => {
826 println!(" โ
Existing build deleted successfully");
827 }
828 Err(e) => {
829 return Err(WorkflowError::Build(e));
830 }
831 }
832
833 println!(" โณ Waiting for build deletion to be fully processed...");
835 self.wait_for_build_deletion(app_id, sandbox_id).await?;
836
837 println!(" โ Creating new build...");
839 self.create_build_for_upload(app_id, sandbox_id, version)
840 .await
841 } else {
842 return Err(WorkflowError::Workflow(format!(
843 "Build {} has status '{}' which is not safe to delete with policy {} (0=Never, 1=Safe only, 2=Except Results Ready). Cannot proceed with upload.",
844 build.build_id, build_status, deletion_policy
845 )));
846 }
847 }
848 Err(crate::build::BuildError::BuildNotFound) => {
849 println!(" โ No build found, creating new build...");
850 self.create_build_for_upload(app_id, sandbox_id, version)
851 .await
852 }
853 Err(e) => {
854 println!(" โ ๏ธ Error checking build existence: {e}");
855 println!(" โ Attempting to create new build...");
857 self.create_build_for_upload(app_id, sandbox_id, version)
858 .await
859 }
860 }
861 }
862
863 async fn create_build_for_upload(
875 &self,
876 app_id: &str,
877 sandbox_id: Option<&str>,
878 version: Option<&str>,
879 ) -> WorkflowResult<Build> {
880 let build_api = self.client.build_api();
881
882 let build_version = version.map(|v| v.to_string()).unwrap_or_else(|| {
883 let timestamp = std::time::SystemTime::now()
885 .duration_since(std::time::UNIX_EPOCH)
886 .unwrap()
887 .as_secs();
888 format!("build-{timestamp}")
889 });
890
891 let create_request = crate::build::CreateBuildRequest {
892 app_id: app_id.to_string(),
893 version: Some(build_version.clone()),
894 lifecycle_stage: Some(crate::build::default_lifecycle_stage().to_string()),
895 launch_date: None,
896 sandbox_id: sandbox_id.map(|s| s.to_string()),
897 };
898
899 match build_api.create_build(create_request).await {
900 Ok(build) => {
901 println!(" โ
Build created successfully: {}", build.build_id);
902 println!(" Version: {build_version}");
903 if sandbox_id.is_some() {
904 println!(" Type: Sandbox build");
905 } else {
906 println!(" Type: Application build");
907 }
908 Ok(build)
909 }
910 Err(e) => {
911 println!(" โ Build creation failed: {e}");
912 Err(WorkflowError::Build(e))
913 }
914 }
915 }
916
917 async fn wait_for_build_deletion(
931 &self,
932 app_id: &str,
933 sandbox_id: Option<&str>,
934 ) -> WorkflowResult<()> {
935 let build_api = self.client.build_api();
936 let max_attempts = 5;
937 let delay_seconds = 3;
938
939 let sleep_duration = tokio::time::Duration::from_secs(delay_seconds);
941
942 for attempt in 1..=max_attempts {
943 tokio::time::sleep(sleep_duration).await;
945
946 let get_request = crate::build::GetBuildInfoRequest {
948 app_id: app_id.to_string(),
949 build_id: None,
950 sandbox_id: sandbox_id.map(|s| s.to_string()),
951 };
952
953 match build_api.get_build_info(get_request).await {
954 Ok(_build) => {
955 if attempt < max_attempts {
957 println!(
958 " โณ Build still exists, waiting {delay_seconds} more seconds... (attempt {attempt}/{max_attempts})"
959 );
960 } else {
961 println!(
962 " โ ๏ธ Build still exists after {max_attempts} attempts, proceeding anyway"
963 );
964 }
965 }
966 Err(crate::build::BuildError::BuildNotFound) => {
967 println!(
969 " โ
Build deletion confirmed (attempt {attempt}/{max_attempts})"
970 );
971 return Ok(());
972 }
973 Err(e) => {
974 println!(" โ ๏ธ Error checking build status: {e} (attempt {attempt})");
976 }
977 }
978 }
979
980 Ok(())
983 }
984
985 pub async fn upload_large_file_with_build_management(
1002 &self,
1003 app_id: &str,
1004 sandbox_id: Option<&str>,
1005 file_path: &str,
1006 filename: Option<&str>,
1007 version: Option<&str>,
1008 ) -> WorkflowResult<crate::scan::UploadedFile> {
1009 println!("๐ Starting large file upload with build management");
1010 println!(" File: {file_path}");
1011 if let Some(sandbox_id) = sandbox_id {
1012 println!(" Target: Sandbox {sandbox_id}");
1013 } else {
1014 println!(" Target: Application {app_id}");
1015 }
1016
1017 let _build = self
1019 .ensure_build_exists(app_id, sandbox_id, version)
1020 .await?;
1021
1022 println!("\n๐ค Uploading file using uploadlargefile.do...");
1024 let scan_api = self.client.scan_api();
1025
1026 let upload_request = crate::scan::UploadLargeFileRequest {
1027 app_id: app_id.to_string(),
1028 file_path: file_path.to_string(),
1029 filename: filename.map(|s| s.to_string()),
1030 sandbox_id: sandbox_id.map(|s| s.to_string()),
1031 };
1032
1033 match scan_api.upload_large_file(upload_request).await {
1034 Ok(uploaded_file) => {
1035 println!(" โ
Large file uploaded successfully:");
1036 println!(" File ID: {}", uploaded_file.file_id);
1037 println!(" File Name: {}", uploaded_file.file_name);
1038 println!(" Size: {} bytes", uploaded_file.file_size);
1039 Ok(uploaded_file)
1040 }
1041 Err(e) => {
1042 println!(" โ Large file upload failed: {e}");
1043 Err(WorkflowError::Scan(e))
1044 }
1045 }
1046 }
1047
1048 pub async fn upload_large_file_with_progress_and_build_management<F>(
1066 &self,
1067 app_id: &str,
1068 sandbox_id: Option<&str>,
1069 file_path: &str,
1070 filename: Option<&str>,
1071 version: Option<&str>,
1072 progress_callback: F,
1073 ) -> WorkflowResult<crate::scan::UploadedFile>
1074 where
1075 F: Fn(u64, u64, f64) + Send + Sync,
1076 {
1077 println!("๐ Starting large file upload with progress tracking and build management");
1078 println!(" File: {file_path}");
1079
1080 let _build = self
1082 .ensure_build_exists(app_id, sandbox_id, version)
1083 .await?;
1084
1085 println!("\n๐ค Uploading file with progress tracking...");
1087 let scan_api = self.client.scan_api();
1088
1089 let upload_request = crate::scan::UploadLargeFileRequest {
1090 app_id: app_id.to_string(),
1091 file_path: file_path.to_string(),
1092 filename: filename.map(|s| s.to_string()),
1093 sandbox_id: sandbox_id.map(|s| s.to_string()),
1094 };
1095
1096 match scan_api
1097 .upload_large_file_with_progress(upload_request, progress_callback)
1098 .await
1099 {
1100 Ok(uploaded_file) => {
1101 println!(" โ
Large file uploaded successfully with progress tracking");
1102 Ok(uploaded_file)
1103 }
1104 Err(e) => {
1105 println!(" โ Large file upload with progress failed: {e}");
1106 Err(WorkflowError::Scan(e))
1107 }
1108 }
1109 }
1110
1111 pub async fn upload_file_with_smart_build_management(
1128 &self,
1129 app_id: &str,
1130 sandbox_id: Option<&str>,
1131 file_path: &str,
1132 filename: Option<&str>,
1133 version: Option<&str>,
1134 ) -> WorkflowResult<crate::scan::UploadedFile> {
1135 let file_metadata = std::fs::metadata(file_path)
1137 .map_err(|e| WorkflowError::Workflow(format!("Cannot access file {file_path}: {e}")))?;
1138
1139 let file_size = file_metadata.len();
1140 const LARGE_FILE_THRESHOLD: u64 = 100 * 1024 * 1024; println!("๐ File size: {file_size} bytes");
1143
1144 if file_size > LARGE_FILE_THRESHOLD {
1145 println!("๐ฆ Using large file upload (uploadlargefile.do) with build management");
1146 self.upload_large_file_with_build_management(
1147 app_id, sandbox_id, file_path, filename, version,
1148 )
1149 .await
1150 } else {
1151 println!("๐ฆ Using standard file upload (uploadfile.do)");
1152 let scan_api = self.client.scan_api();
1153
1154 let upload_request = crate::scan::UploadFileRequest {
1155 app_id: app_id.to_string(),
1156 file_path: file_path.to_string(),
1157 save_as: filename.map(|s| s.to_string()),
1158 sandbox_id: sandbox_id.map(|s| s.to_string()),
1159 };
1160
1161 match scan_api.upload_file(upload_request).await {
1162 Ok(uploaded_file) => {
1163 println!(" โ
File uploaded successfully via uploadfile.do");
1164 Ok(uploaded_file)
1165 }
1166 Err(e) => {
1167 println!(" โ Standard upload failed: {e}");
1168 Err(WorkflowError::Scan(e))
1169 }
1170 }
1171 }
1172 }
1173
1174 pub async fn get_or_create_build(
1188 &self,
1189 app_id: &str,
1190 sandbox_id: Option<&str>,
1191 version: Option<&str>,
1192 ) -> WorkflowResult<Build> {
1193 self.ensure_build_exists(app_id, sandbox_id, version).await
1194 }
1195}
1196
1197#[cfg(test)]
1198mod tests {
1199 use super::*;
1200
1201 #[test]
1202 fn test_workflow_config_builder() {
1203 let config = WorkflowConfig::new("MyApp".to_string(), "MySandbox".to_string())
1204 .with_business_criticality(BusinessCriticality::High)
1205 .with_app_description("Test application".to_string())
1206 .with_file("test.jar".to_string())
1207 .with_auto_scan(false);
1208
1209 assert_eq!(config.app_name, "MyApp");
1210 assert_eq!(config.sandbox_name, "MySandbox");
1211 assert_eq!(
1212 config.business_criticality as i32,
1213 BusinessCriticality::High as i32
1214 );
1215 assert_eq!(config.app_description, Some("Test application".to_string()));
1216 assert_eq!(config.file_paths, vec!["test.jar"]);
1217 assert!(!config.auto_scan);
1218 }
1219
1220 #[test]
1221 fn test_workflow_error_display() {
1222 let error = WorkflowError::NotFound("Application not found".to_string());
1223 assert_eq!(error.to_string(), "Not found: Application not found");
1224
1225 let error = WorkflowError::AccessDenied("Permission denied".to_string());
1226 assert_eq!(error.to_string(), "Access denied: Permission denied");
1227
1228 let error = WorkflowError::Workflow("Custom error".to_string());
1229 assert_eq!(error.to_string(), "Workflow error: Custom error");
1230 }
1231}