1use chrono::{DateTime, Utc};
8use quick_xml::Reader;
9use quick_xml::events::Event;
10use serde::{Deserialize, Serialize};
11use std::path::Path;
12
13use crate::{VeracodeClient, VeracodeError};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct UploadedFile {
18 pub file_id: String,
20 pub file_name: String,
22 pub file_size: u64,
24 pub uploaded: DateTime<Utc>,
26 pub file_status: String,
28 pub md5: Option<String>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct PreScanResults {
35 pub build_id: String,
37 pub app_id: String,
39 pub sandbox_id: Option<String>,
41 pub status: String,
43 pub modules: Vec<ScanModule>,
45 pub messages: Vec<PreScanMessage>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ScanModule {
52 pub id: String,
54 pub name: String,
56 pub module_type: String,
58 pub is_fatal: bool,
60 pub selected: bool,
62 pub size: Option<u64>,
64 pub platform: Option<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct PreScanMessage {
71 pub severity: String,
73 pub text: String,
75 pub module_name: Option<String>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ScanInfo {
82 pub build_id: String,
84 pub app_id: String,
86 pub sandbox_id: Option<String>,
88 pub status: String,
90 pub scan_type: String,
92 pub analysis_unit_id: Option<String>,
94 pub scan_progress_percentage: Option<u32>,
96 pub scan_start: Option<DateTime<Utc>>,
98 pub scan_complete: Option<DateTime<Utc>>,
100 pub total_lines_of_code: Option<u64>,
102}
103
104#[derive(Debug, Clone)]
106pub struct UploadFileRequest {
107 pub app_id: String,
109 pub file_path: String,
111 pub save_as: Option<String>,
113 pub sandbox_id: Option<String>,
115}
116
117#[derive(Debug, Clone)]
119pub struct UploadLargeFileRequest {
120 pub app_id: String,
122 pub file_path: String,
124 pub filename: Option<String>,
126 pub sandbox_id: Option<String>,
128}
129
130#[derive(Debug, Clone)]
132pub struct UploadProgress {
133 pub bytes_uploaded: u64,
135 pub total_bytes: u64,
137 pub percentage: f64,
139}
140
141pub trait UploadProgressCallback: Send + Sync {
143 fn on_progress(&self, progress: UploadProgress);
145 fn on_completed(&self);
147 fn on_error(&self, error: &str);
149}
150
151#[derive(Debug, Clone)]
153pub struct BeginPreScanRequest {
154 pub app_id: String,
156 pub sandbox_id: Option<String>,
158 pub auto_scan: Option<bool>,
160 pub scan_all_nonfatal_top_level_modules: Option<bool>,
162 pub include_new_modules: Option<bool>,
164}
165
166#[derive(Debug, Clone)]
168pub struct BeginScanRequest {
169 pub app_id: String,
171 pub sandbox_id: Option<String>,
173 pub modules: Option<String>,
175 pub scan_all_top_level_modules: Option<bool>,
177 pub scan_all_nonfatal_top_level_modules: Option<bool>,
179 pub scan_previously_selected_modules: Option<bool>,
181}
182
183#[derive(Debug)]
185pub enum ScanError {
186 Api(VeracodeError),
188 FileNotFound(String),
190 InvalidFileFormat(String),
192 UploadFailed(String),
194 ScanFailed(String),
196 PreScanFailed(String),
198 BuildNotFound,
200 ApplicationNotFound,
202 SandboxNotFound,
204 Unauthorized,
206 PermissionDenied,
208 InvalidParameter(String),
210 FileTooLarge(String),
212 UploadInProgress,
214 ScanInProgress,
216 BuildCreationFailed(String),
218 ChunkedUploadFailed(String),
220}
221
222impl std::fmt::Display for ScanError {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 match self {
225 ScanError::Api(err) => write!(f, "API error: {err}"),
226 ScanError::FileNotFound(path) => write!(f, "File not found: {path}"),
227 ScanError::InvalidFileFormat(msg) => write!(f, "Invalid file format: {msg}"),
228 ScanError::UploadFailed(msg) => write!(f, "Upload failed: {msg}"),
229 ScanError::ScanFailed(msg) => write!(f, "Scan failed: {msg}"),
230 ScanError::PreScanFailed(msg) => write!(f, "Pre-scan failed: {msg}"),
231 ScanError::BuildNotFound => write!(f, "Build not found"),
232 ScanError::ApplicationNotFound => write!(f, "Application not found"),
233 ScanError::SandboxNotFound => write!(f, "Sandbox not found"),
234 ScanError::Unauthorized => write!(f, "Unauthorized access"),
235 ScanError::PermissionDenied => write!(f, "Permission denied"),
236 ScanError::InvalidParameter(msg) => write!(f, "Invalid parameter: {msg}"),
237 ScanError::FileTooLarge(msg) => write!(f, "File too large: {msg}"),
238 ScanError::UploadInProgress => write!(f, "Upload or prescan already in progress"),
239 ScanError::ScanInProgress => write!(f, "Scan in progress, cannot upload"),
240 ScanError::BuildCreationFailed(msg) => write!(f, "Build creation failed: {msg}"),
241 ScanError::ChunkedUploadFailed(msg) => write!(f, "Chunked upload failed: {msg}"),
242 }
243 }
244}
245
246impl std::error::Error for ScanError {}
247
248impl From<VeracodeError> for ScanError {
249 fn from(err: VeracodeError) -> Self {
250 ScanError::Api(err)
251 }
252}
253
254impl From<reqwest::Error> for ScanError {
255 fn from(err: reqwest::Error) -> Self {
256 ScanError::Api(VeracodeError::Http(err))
257 }
258}
259
260impl From<serde_json::Error> for ScanError {
261 fn from(err: serde_json::Error) -> Self {
262 ScanError::Api(VeracodeError::Serialization(err))
263 }
264}
265
266impl From<std::io::Error> for ScanError {
267 fn from(err: std::io::Error) -> Self {
268 ScanError::FileNotFound(err.to_string())
269 }
270}
271
272pub struct ScanApi {
274 client: VeracodeClient,
275}
276
277impl ScanApi {
278 pub fn new(client: VeracodeClient) -> Self {
280 Self { client }
281 }
282
283 pub async fn upload_file(&self, request: UploadFileRequest) -> Result<UploadedFile, ScanError> {
293 if !Path::new(&request.file_path).exists() {
295 return Err(ScanError::FileNotFound(request.file_path));
296 }
297
298 let endpoint = "/api/5.0/uploadfile.do";
299
300 let mut query_params = Vec::new();
302 query_params.push(("app_id", request.app_id.as_str()));
303
304 if let Some(sandbox_id) = &request.sandbox_id {
305 query_params.push(("sandbox_id", sandbox_id.as_str()));
306 }
307
308 if let Some(save_as) = &request.save_as {
309 query_params.push(("save_as", save_as.as_str()));
310 }
311
312 let file_data = std::fs::read(&request.file_path)?;
314
315 let filename = Path::new(&request.file_path)
317 .file_name()
318 .and_then(|f| f.to_str())
319 .unwrap_or("file");
320
321 let response = self
322 .client
323 .upload_file_with_query_params(endpoint, &query_params, "file", filename, file_data)
324 .await?;
325
326 let status = response.status().as_u16();
327 match status {
328 200 => {
329 let response_text = response.text().await?;
330 self.parse_upload_response(&response_text, &request.file_path)
331 }
332 400 => {
333 let error_text = response.text().await.unwrap_or_default();
334 Err(ScanError::InvalidParameter(error_text))
335 }
336 401 => Err(ScanError::Unauthorized),
337 403 => Err(ScanError::PermissionDenied),
338 404 => {
339 if request.sandbox_id.is_some() {
340 Err(ScanError::SandboxNotFound)
341 } else {
342 Err(ScanError::ApplicationNotFound)
343 }
344 }
345 _ => {
346 let error_text = response.text().await.unwrap_or_default();
347 Err(ScanError::UploadFailed(format!(
348 "HTTP {status}: {error_text}"
349 )))
350 }
351 }
352 }
353
354 pub async fn upload_large_file(
368 &self,
369 request: UploadLargeFileRequest,
370 ) -> Result<UploadedFile, ScanError> {
371 if !Path::new(&request.file_path).exists() {
373 return Err(ScanError::FileNotFound(request.file_path));
374 }
375
376 let file_metadata = std::fs::metadata(&request.file_path)?;
378 let file_size = file_metadata.len();
379 const MAX_FILE_SIZE: u64 = 2 * 1024 * 1024 * 1024; if file_size > MAX_FILE_SIZE {
382 return Err(ScanError::FileTooLarge(format!(
383 "File size {file_size} bytes exceeds 2GB limit"
384 )));
385 }
386
387 let endpoint = "uploadlargefile.do"; let mut query_params = Vec::new();
391 query_params.push(("app_id", request.app_id.as_str()));
392
393 if let Some(sandbox_id) = &request.sandbox_id {
394 query_params.push(("sandbox_id", sandbox_id.as_str()));
395 }
396
397 if let Some(filename) = &request.filename {
398 query_params.push(("filename", filename.as_str()));
399 }
400
401 let file_data = std::fs::read(&request.file_path)?;
403
404 let response = self
405 .client
406 .upload_file_binary(endpoint, &query_params, file_data, "binary/octet-stream")
407 .await?;
408
409 let status = response.status().as_u16();
410 match status {
411 200 => {
412 let response_text = response.text().await?;
413 self.parse_upload_response(&response_text, &request.file_path)
414 }
415 400 => {
416 let error_text = response.text().await.unwrap_or_default();
417 if error_text.contains("upload or prescan in progress") {
418 Err(ScanError::UploadInProgress)
419 } else if error_text.contains("scan in progress") {
420 Err(ScanError::ScanInProgress)
421 } else {
422 Err(ScanError::InvalidParameter(error_text))
423 }
424 }
425 401 => Err(ScanError::Unauthorized),
426 403 => Err(ScanError::PermissionDenied),
427 404 => {
428 if request.sandbox_id.is_some() {
429 Err(ScanError::SandboxNotFound)
430 } else {
431 Err(ScanError::ApplicationNotFound)
432 }
433 }
434 413 => Err(ScanError::FileTooLarge(
435 "File size exceeds server limits".to_string(),
436 )),
437 _ => {
438 let error_text = response.text().await.unwrap_or_default();
439 Err(ScanError::UploadFailed(format!(
440 "HTTP {status}: {error_text}"
441 )))
442 }
443 }
444 }
445
446 pub async fn upload_large_file_with_progress<F>(
460 &self,
461 request: UploadLargeFileRequest,
462 progress_callback: F,
463 ) -> Result<UploadedFile, ScanError>
464 where
465 F: Fn(u64, u64, f64) + Send + Sync,
466 {
467 if !Path::new(&request.file_path).exists() {
469 return Err(ScanError::FileNotFound(request.file_path));
470 }
471
472 let file_metadata = std::fs::metadata(&request.file_path)?;
474 let file_size = file_metadata.len();
475 const MAX_FILE_SIZE: u64 = 2 * 1024 * 1024 * 1024; if file_size > MAX_FILE_SIZE {
478 return Err(ScanError::FileTooLarge(format!(
479 "File size {file_size} bytes exceeds 2GB limit"
480 )));
481 }
482
483 let endpoint = "uploadlargefile.do";
484
485 let mut query_params = Vec::new();
487 query_params.push(("app_id", request.app_id.as_str()));
488
489 if let Some(sandbox_id) = &request.sandbox_id {
490 query_params.push(("sandbox_id", sandbox_id.as_str()));
491 }
492
493 if let Some(filename) = &request.filename {
494 query_params.push(("filename", filename.as_str()));
495 }
496
497 let response = self
498 .client
499 .upload_large_file_chunked(
500 endpoint,
501 &query_params,
502 &request.file_path,
503 Some("binary/octet-stream"),
504 Some(progress_callback),
505 )
506 .await?;
507
508 let status = response.status().as_u16();
509 match status {
510 200 => {
511 let response_text = response.text().await?;
512 self.parse_upload_response(&response_text, &request.file_path)
513 }
514 400 => {
515 let error_text = response.text().await.unwrap_or_default();
516 if error_text.contains("upload or prescan in progress") {
517 Err(ScanError::UploadInProgress)
518 } else if error_text.contains("scan in progress") {
519 Err(ScanError::ScanInProgress)
520 } else {
521 Err(ScanError::InvalidParameter(error_text))
522 }
523 }
524 401 => Err(ScanError::Unauthorized),
525 403 => Err(ScanError::PermissionDenied),
526 404 => {
527 if request.sandbox_id.is_some() {
528 Err(ScanError::SandboxNotFound)
529 } else {
530 Err(ScanError::ApplicationNotFound)
531 }
532 }
533 413 => Err(ScanError::FileTooLarge(
534 "File size exceeds server limits".to_string(),
535 )),
536 _ => {
537 let error_text = response.text().await.unwrap_or_default();
538 Err(ScanError::ChunkedUploadFailed(format!(
539 "HTTP {status}: {error_text}"
540 )))
541 }
542 }
543 }
544
545 pub async fn upload_file_smart(
558 &self,
559 request: UploadFileRequest,
560 ) -> Result<UploadedFile, ScanError> {
561 if !Path::new(&request.file_path).exists() {
563 return Err(ScanError::FileNotFound(request.file_path));
564 }
565
566 let file_metadata = std::fs::metadata(&request.file_path)?;
568 let file_size = file_metadata.len();
569
570 const LARGE_FILE_THRESHOLD: u64 = 100 * 1024 * 1024; if file_size > LARGE_FILE_THRESHOLD {
574 let large_request = UploadLargeFileRequest {
576 app_id: request.app_id.clone(),
577 file_path: request.file_path.clone(),
578 filename: request.save_as.clone(),
579 sandbox_id: request.sandbox_id.clone(),
580 };
581
582 match self.upload_large_file(large_request).await {
584 Ok(result) => Ok(result),
585 Err(ScanError::Api(_)) => {
586 self.upload_file(request).await
588 }
589 Err(e) => Err(e),
590 }
591 } else {
592 self.upload_file(request).await
594 }
595 }
596
597 pub async fn begin_prescan(&self, request: BeginPreScanRequest) -> Result<(), ScanError> {
607 let endpoint = "/api/5.0/beginprescan.do";
608
609 let mut query_params = Vec::new();
611 query_params.push(("app_id", request.app_id.as_str()));
612
613 if let Some(sandbox_id) = &request.sandbox_id {
614 query_params.push(("sandbox_id", sandbox_id.as_str()));
615 }
616
617 if let Some(auto_scan) = request.auto_scan {
618 query_params.push(("auto_scan", if auto_scan { "true" } else { "false" }));
619 }
620
621 if let Some(scan_all) = request.scan_all_nonfatal_top_level_modules {
622 query_params.push((
623 "scan_all_nonfatal_top_level_modules",
624 if scan_all { "true" } else { "false" },
625 ));
626 }
627
628 if let Some(include_new) = request.include_new_modules {
629 query_params.push((
630 "include_new_modules",
631 if include_new { "true" } else { "false" },
632 ));
633 }
634
635 let response = self.client.get_with_params(endpoint, &query_params).await?;
636
637 let status = response.status().as_u16();
638 match status {
639 200 => {
640 let response_text = response.text().await?;
641 self.validate_scan_response(&response_text)?;
644 Ok(())
645 }
646 400 => {
647 let error_text = response.text().await.unwrap_or_default();
648 Err(ScanError::InvalidParameter(error_text))
649 }
650 401 => Err(ScanError::Unauthorized),
651 403 => Err(ScanError::PermissionDenied),
652 404 => {
653 if request.sandbox_id.is_some() {
654 Err(ScanError::SandboxNotFound)
655 } else {
656 Err(ScanError::ApplicationNotFound)
657 }
658 }
659 _ => {
660 let error_text = response.text().await.unwrap_or_default();
661 Err(ScanError::PreScanFailed(format!(
662 "HTTP {status}: {error_text}"
663 )))
664 }
665 }
666 }
667
668 pub async fn get_prescan_results(
680 &self,
681 app_id: &str,
682 sandbox_id: Option<&str>,
683 build_id: Option<&str>,
684 ) -> Result<PreScanResults, ScanError> {
685 let endpoint = "/api/5.0/getprescanresults.do";
686
687 let mut params = Vec::new();
688 params.push(("app_id", app_id));
689
690 if let Some(sandbox_id) = sandbox_id {
691 params.push(("sandbox_id", sandbox_id));
692 }
693
694 if let Some(build_id) = build_id {
695 params.push(("build_id", build_id));
696 }
697
698 let response = self.client.get_with_params(endpoint, ¶ms).await?;
699
700 let status = response.status().as_u16();
701 match status {
702 200 => {
703 let response_text = response.text().await?;
704 self.parse_prescan_results(&response_text, app_id, sandbox_id)
705 }
706 401 => Err(ScanError::Unauthorized),
707 403 => Err(ScanError::PermissionDenied),
708 404 => {
709 if sandbox_id.is_some() {
710 Err(ScanError::SandboxNotFound)
711 } else {
712 Err(ScanError::ApplicationNotFound)
713 }
714 }
715 _ => {
716 let error_text = response.text().await.unwrap_or_default();
717 Err(ScanError::PreScanFailed(format!(
718 "HTTP {status}: {error_text}"
719 )))
720 }
721 }
722 }
723
724 pub async fn begin_scan(&self, request: BeginScanRequest) -> Result<(), ScanError> {
734 let endpoint = "/api/5.0/beginscan.do";
735
736 let mut query_params = Vec::new();
738 query_params.push(("app_id", request.app_id.as_str()));
739
740 if let Some(sandbox_id) = &request.sandbox_id {
741 query_params.push(("sandbox_id", sandbox_id.as_str()));
742 }
743
744 if let Some(modules) = &request.modules {
745 query_params.push(("modules", modules.as_str()));
746 }
747
748 if let Some(scan_all) = request.scan_all_top_level_modules {
749 query_params.push((
750 "scan_all_top_level_modules",
751 if scan_all { "true" } else { "false" },
752 ));
753 }
754
755 if let Some(scan_all_nonfatal) = request.scan_all_nonfatal_top_level_modules {
756 query_params.push((
757 "scan_all_nonfatal_top_level_modules",
758 if scan_all_nonfatal { "true" } else { "false" },
759 ));
760 }
761
762 if let Some(scan_previous) = request.scan_previously_selected_modules {
763 query_params.push((
764 "scan_previously_selected_modules",
765 if scan_previous { "true" } else { "false" },
766 ));
767 }
768
769 let response = self.client.get_with_params(endpoint, &query_params).await?;
770
771 let status = response.status().as_u16();
772 match status {
773 200 => {
774 let response_text = response.text().await?;
775 self.validate_scan_response(&response_text)?;
778 Ok(())
779 }
780 400 => {
781 let error_text = response.text().await.unwrap_or_default();
782 Err(ScanError::InvalidParameter(error_text))
783 }
784 401 => Err(ScanError::Unauthorized),
785 403 => Err(ScanError::PermissionDenied),
786 404 => {
787 if request.sandbox_id.is_some() {
788 Err(ScanError::SandboxNotFound)
789 } else {
790 Err(ScanError::ApplicationNotFound)
791 }
792 }
793 _ => {
794 let error_text = response.text().await.unwrap_or_default();
795 Err(ScanError::ScanFailed(format!(
796 "HTTP {status}: {error_text}"
797 )))
798 }
799 }
800 }
801
802 pub async fn get_file_list(
814 &self,
815 app_id: &str,
816 sandbox_id: Option<&str>,
817 build_id: Option<&str>,
818 ) -> Result<Vec<UploadedFile>, ScanError> {
819 let endpoint = "/api/5.0/getfilelist.do";
820
821 let mut params = Vec::new();
822 params.push(("app_id", app_id));
823
824 if let Some(sandbox_id) = sandbox_id {
825 params.push(("sandbox_id", sandbox_id));
826 }
827
828 if let Some(build_id) = build_id {
829 params.push(("build_id", build_id));
830 }
831
832 let response = self.client.get_with_params(endpoint, ¶ms).await?;
833
834 let status = response.status().as_u16();
835 match status {
836 200 => {
837 let response_text = response.text().await?;
838 self.parse_file_list(&response_text)
839 }
840 401 => Err(ScanError::Unauthorized),
841 403 => Err(ScanError::PermissionDenied),
842 404 => {
843 if sandbox_id.is_some() {
844 Err(ScanError::SandboxNotFound)
845 } else {
846 Err(ScanError::ApplicationNotFound)
847 }
848 }
849 _ => {
850 let error_text = response.text().await.unwrap_or_default();
851 Err(ScanError::Api(VeracodeError::InvalidResponse(format!(
852 "HTTP {status}: {error_text}"
853 ))))
854 }
855 }
856 }
857
858 pub async fn remove_file(
870 &self,
871 app_id: &str,
872 file_id: &str,
873 sandbox_id: Option<&str>,
874 ) -> Result<(), ScanError> {
875 let endpoint = "/api/5.0/removefile.do";
876
877 let mut query_params = Vec::new();
879 query_params.push(("app_id", app_id));
880 query_params.push(("file_id", file_id));
881
882 if let Some(sandbox_id) = sandbox_id {
883 query_params.push(("sandbox_id", sandbox_id));
884 }
885
886 let response = self.client.get_with_params(endpoint, &query_params).await?;
887
888 let status = response.status().as_u16();
889 match status {
890 200 => Ok(()),
891 400 => {
892 let error_text = response.text().await.unwrap_or_default();
893 Err(ScanError::InvalidParameter(error_text))
894 }
895 401 => Err(ScanError::Unauthorized),
896 403 => Err(ScanError::PermissionDenied),
897 404 => Err(ScanError::FileNotFound(file_id.to_string())),
898 _ => {
899 let error_text = response.text().await.unwrap_or_default();
900 Err(ScanError::Api(VeracodeError::InvalidResponse(format!(
901 "HTTP {status}: {error_text}"
902 ))))
903 }
904 }
905 }
906
907 pub async fn delete_build(
921 &self,
922 app_id: &str,
923 build_id: &str,
924 sandbox_id: Option<&str>,
925 ) -> Result<(), ScanError> {
926 let endpoint = "/api/5.0/deletebuild.do";
927
928 let mut query_params = Vec::new();
930 query_params.push(("app_id", app_id));
931 query_params.push(("build_id", build_id));
932
933 if let Some(sandbox_id) = sandbox_id {
934 query_params.push(("sandbox_id", sandbox_id));
935 }
936
937 let response = self.client.get_with_params(endpoint, &query_params).await?;
938
939 let status = response.status().as_u16();
940 match status {
941 200 => Ok(()),
942 400 => {
943 let error_text = response.text().await.unwrap_or_default();
944 Err(ScanError::InvalidParameter(error_text))
945 }
946 401 => Err(ScanError::Unauthorized),
947 403 => Err(ScanError::PermissionDenied),
948 404 => Err(ScanError::BuildNotFound),
949 _ => {
950 let error_text = response.text().await.unwrap_or_default();
951 Err(ScanError::Api(VeracodeError::InvalidResponse(format!(
952 "HTTP {status}: {error_text}"
953 ))))
954 }
955 }
956 }
957
958 pub async fn delete_all_builds(
972 &self,
973 app_id: &str,
974 sandbox_id: Option<&str>,
975 ) -> Result<(), ScanError> {
976 let build_info = self.get_build_info(app_id, None, sandbox_id).await?;
978
979 if !build_info.build_id.is_empty() && build_info.build_id != "unknown" {
980 println!(" 🗑️ Deleting build: {}", build_info.build_id);
981 self.delete_build(app_id, &build_info.build_id, sandbox_id)
982 .await?;
983 }
984
985 Ok(())
986 }
987
988 pub async fn get_build_info(
1000 &self,
1001 app_id: &str,
1002 build_id: Option<&str>,
1003 sandbox_id: Option<&str>,
1004 ) -> Result<ScanInfo, ScanError> {
1005 let endpoint = "/api/5.0/getbuildinfo.do";
1006
1007 let mut params = Vec::new();
1008 params.push(("app_id", app_id));
1009
1010 if let Some(build_id) = build_id {
1011 params.push(("build_id", build_id));
1012 }
1013
1014 if let Some(sandbox_id) = sandbox_id {
1015 params.push(("sandbox_id", sandbox_id));
1016 }
1017
1018 let response = self.client.get_with_params(endpoint, ¶ms).await?;
1019
1020 let status = response.status().as_u16();
1021 match status {
1022 200 => {
1023 let response_text = response.text().await?;
1024 self.parse_build_info(&response_text, app_id, sandbox_id)
1025 }
1026 401 => Err(ScanError::Unauthorized),
1027 403 => Err(ScanError::PermissionDenied),
1028 404 => Err(ScanError::BuildNotFound),
1029 _ => {
1030 let error_text = response.text().await.unwrap_or_default();
1031 Err(ScanError::Api(VeracodeError::InvalidResponse(format!(
1032 "HTTP {status}: {error_text}"
1033 ))))
1034 }
1035 }
1036 }
1037
1038 fn parse_upload_response(&self, xml: &str, file_path: &str) -> Result<UploadedFile, ScanError> {
1041 let mut reader = Reader::from_str(xml);
1042 reader.config_mut().trim_text(true);
1043
1044 let mut buf = Vec::new();
1045 let mut file_id = None;
1046 let mut file_status = "Unknown".to_string();
1047 let mut _md5: Option<String> = None;
1048
1049 loop {
1050 match reader.read_event_into(&mut buf) {
1051 Ok(Event::Start(ref e)) => {
1052 if e.name().as_ref() == b"file" {
1053 for attr in e.attributes().flatten() {
1055 if attr.key.as_ref() == b"file_id" {
1056 file_id = Some(String::from_utf8_lossy(&attr.value).to_string());
1057 }
1058 }
1059 }
1060 }
1061 Ok(Event::Text(e)) => {
1062 let text = std::str::from_utf8(&e).unwrap_or_default();
1063 if text.contains("successfully uploaded") {
1065 file_status = "Uploaded".to_string();
1066 } else if text.contains("error") || text.contains("failed") {
1067 file_status = "Failed".to_string();
1068 }
1069 }
1070 Ok(Event::Eof) => break,
1071 Err(e) => {
1072 eprintln!("Error parsing XML: {e}");
1073 break;
1074 }
1075 _ => {}
1076 }
1077 buf.clear();
1078 }
1079
1080 let filename = Path::new(file_path)
1081 .file_name()
1082 .and_then(|f| f.to_str())
1083 .unwrap_or("file")
1084 .to_string();
1085
1086 Ok(UploadedFile {
1087 file_id: file_id.unwrap_or_else(|| format!("file_{}", chrono::Utc::now().timestamp())),
1088 file_name: filename,
1089 file_size: std::fs::metadata(file_path).map(|m| m.len()).unwrap_or(0),
1090 uploaded: Utc::now(),
1091 file_status,
1092 md5: None,
1093 })
1094 }
1095
1096 #[allow(dead_code)]
1097 fn parse_build_id_response(&self, xml: &str) -> Result<String, ScanError> {
1098 let mut reader = Reader::from_str(xml);
1099 reader.config_mut().trim_text(true);
1100
1101 let mut buf = Vec::new();
1102 let mut build_id = None;
1103
1104 loop {
1105 match reader.read_event_into(&mut buf) {
1106 Ok(Event::Start(ref e)) => {
1107 match e.name().as_ref() {
1108 b"buildinfo" | b"build" => {
1109 for attr in e.attributes().flatten() {
1111 if attr.key.as_ref() == b"build_id" {
1112 build_id =
1113 Some(String::from_utf8_lossy(&attr.value).to_string());
1114 }
1115 }
1116 }
1117 _ => {}
1118 }
1119 }
1120 Ok(Event::Eof) => break,
1121 Err(e) => {
1122 eprintln!("Error parsing XML: {e}");
1123 break;
1124 }
1125 _ => {}
1126 }
1127 buf.clear();
1128 }
1129
1130 build_id
1131 .ok_or_else(|| ScanError::PreScanFailed("No build_id found in response".to_string()))
1132 }
1133
1134 fn validate_scan_response(&self, xml: &str) -> Result<(), ScanError> {
1136 if xml.contains("<error>") {
1138 let mut reader = Reader::from_str(xml);
1140 reader.config_mut().trim_text(true);
1141
1142 let mut buf = Vec::new();
1143 let mut in_error = false;
1144 let mut error_message = String::new();
1145
1146 loop {
1147 match reader.read_event_into(&mut buf) {
1148 Ok(Event::Start(ref e)) if e.name().as_ref() == b"error" => {
1149 in_error = true;
1150 }
1151 Ok(Event::Text(ref e)) if in_error => {
1152 error_message.push_str(&String::from_utf8_lossy(e));
1153 }
1154 Ok(Event::End(ref e)) if e.name().as_ref() == b"error" => {
1155 break;
1156 }
1157 Ok(Event::Eof) => break,
1158 Err(e) => {
1159 return Err(ScanError::ScanFailed(format!("XML parsing error: {e}")));
1160 }
1161 _ => {}
1162 }
1163 buf.clear();
1164 }
1165
1166 if !error_message.is_empty() {
1167 return Err(ScanError::ScanFailed(error_message));
1168 } else {
1169 return Err(ScanError::ScanFailed(
1170 "Unknown error in scan response".to_string(),
1171 ));
1172 }
1173 }
1174
1175 if xml.contains("<buildinfo") || xml.contains("<build") {
1177 Ok(())
1178 } else {
1179 Err(ScanError::ScanFailed(
1180 "Invalid scan response format".to_string(),
1181 ))
1182 }
1183 }
1184
1185 fn parse_prescan_results(
1186 &self,
1187 xml: &str,
1188 app_id: &str,
1189 sandbox_id: Option<&str>,
1190 ) -> Result<PreScanResults, ScanError> {
1191 if xml.contains("<error>") && xml.contains("Prescan results not available") {
1193 return Ok(PreScanResults {
1195 build_id: String::new(),
1196 app_id: app_id.to_string(),
1197 sandbox_id: sandbox_id.map(|s| s.to_string()),
1198 status: "Pre-Scan Submitted".to_string(), modules: Vec::new(),
1200 messages: Vec::new(),
1201 });
1202 }
1203
1204 let mut reader = Reader::from_str(xml);
1205 reader.config_mut().trim_text(true);
1206
1207 let mut buf = Vec::new();
1208 let mut build_id = None;
1209 let mut modules = Vec::new();
1210 let messages = Vec::new();
1211 let mut has_prescan_results = false;
1212 let mut has_fatal_errors = false;
1213
1214 loop {
1215 match reader.read_event_into(&mut buf) {
1216 Ok(Event::Start(ref e)) => {
1217 match e.name().as_ref() {
1218 b"prescanresults" => {
1219 has_prescan_results = true;
1220 for attr in e.attributes().flatten() {
1222 if attr.key.as_ref() == b"build_id" {
1223 build_id =
1224 Some(String::from_utf8_lossy(&attr.value).to_string());
1225 }
1226 }
1227 }
1228 b"module" => {
1229 let mut module = ScanModule {
1230 id: String::new(),
1231 name: String::new(),
1232 module_type: String::new(),
1233 is_fatal: false,
1234 selected: false,
1235 size: None,
1236 platform: None,
1237 };
1238
1239 for attr in e.attributes().flatten() {
1240 match attr.key.as_ref() {
1241 b"id" => {
1242 module.id = String::from_utf8_lossy(&attr.value).to_string()
1243 }
1244 b"name" => {
1245 module.name =
1246 String::from_utf8_lossy(&attr.value).to_string()
1247 }
1248 b"type" => {
1249 module.module_type =
1250 String::from_utf8_lossy(&attr.value).to_string()
1251 }
1252 b"isfatal" => module.is_fatal = attr.value.as_ref() == b"true",
1253 b"selected" => module.selected = attr.value.as_ref() == b"true",
1254 b"has_fatal_errors" => {
1255 if attr.value.as_ref() == b"true" {
1256 has_fatal_errors = true;
1257 }
1258 }
1259 b"size" => {
1260 if let Ok(size_str) = String::from_utf8(attr.value.to_vec())
1261 {
1262 module.size = size_str.parse().ok();
1263 }
1264 }
1265 b"platform" => {
1266 module.platform =
1267 Some(String::from_utf8_lossy(&attr.value).to_string())
1268 }
1269 _ => {}
1270 }
1271 }
1272 modules.push(module);
1273 }
1274 _ => {}
1275 }
1276 }
1277 Ok(Event::Eof) => break,
1278 Err(e) => {
1279 eprintln!("Error parsing XML: {e}");
1280 break;
1281 }
1282 _ => {}
1283 }
1284 buf.clear();
1285 }
1286
1287 let status = if !has_prescan_results {
1289 "Unknown".to_string()
1290 } else if modules.is_empty() {
1291 "Pre-Scan Failed".to_string()
1293 } else if has_fatal_errors {
1294 "Pre-Scan Failed".to_string()
1296 } else {
1297 "Pre-Scan Success".to_string()
1299 };
1300
1301 Ok(PreScanResults {
1302 build_id: build_id.unwrap_or_else(|| "unknown".to_string()),
1303 app_id: app_id.to_string(),
1304 sandbox_id: sandbox_id.map(|s| s.to_string()),
1305 status,
1306 modules,
1307 messages,
1308 })
1309 }
1310
1311 fn parse_file_list(&self, xml: &str) -> Result<Vec<UploadedFile>, ScanError> {
1312 let mut reader = Reader::from_str(xml);
1313 reader.config_mut().trim_text(true);
1314
1315 let mut buf = Vec::new();
1316 let mut files = Vec::new();
1317
1318 loop {
1319 match reader.read_event_into(&mut buf) {
1320 Ok(Event::Start(ref e)) => {
1321 if e.name().as_ref() == b"file" {
1322 let mut file = UploadedFile {
1323 file_id: String::new(),
1324 file_name: String::new(),
1325 file_size: 0,
1326 uploaded: Utc::now(),
1327 file_status: "Unknown".to_string(),
1328 md5: None,
1329 };
1330
1331 for attr in e.attributes().flatten() {
1332 match attr.key.as_ref() {
1333 b"file_id" => {
1334 file.file_id = String::from_utf8_lossy(&attr.value).to_string()
1335 }
1336 b"file_name" => {
1337 file.file_name =
1338 String::from_utf8_lossy(&attr.value).to_string()
1339 }
1340 b"file_size" => {
1341 if let Ok(size_str) = String::from_utf8(attr.value.to_vec()) {
1342 file.file_size = size_str.parse().unwrap_or(0);
1343 }
1344 }
1345 b"file_status" => {
1346 file.file_status =
1347 String::from_utf8_lossy(&attr.value).to_string()
1348 }
1349 b"md5" => {
1350 file.md5 =
1351 Some(String::from_utf8_lossy(&attr.value).to_string())
1352 }
1353 _ => {}
1354 }
1355 }
1356 files.push(file);
1357 }
1358 }
1359 Ok(Event::Eof) => break,
1360 Err(e) => {
1361 eprintln!("Error parsing XML: {e}");
1362 break;
1363 }
1364 _ => {}
1365 }
1366 buf.clear();
1367 }
1368
1369 Ok(files)
1370 }
1371
1372 fn parse_build_info(
1373 &self,
1374 xml: &str,
1375 app_id: &str,
1376 sandbox_id: Option<&str>,
1377 ) -> Result<ScanInfo, ScanError> {
1378 let mut reader = Reader::from_str(xml);
1379 reader.config_mut().trim_text(true);
1380
1381 let mut buf = Vec::new();
1382 let mut scan_info = ScanInfo {
1383 build_id: String::new(),
1384 app_id: app_id.to_string(),
1385 sandbox_id: sandbox_id.map(|s| s.to_string()),
1386 status: "Unknown".to_string(),
1387 scan_type: "Static".to_string(),
1388 analysis_unit_id: None,
1389 scan_progress_percentage: None,
1390 scan_start: None,
1391 scan_complete: None,
1392 total_lines_of_code: None,
1393 };
1394
1395 let mut inside_build = false;
1396
1397 loop {
1398 match reader.read_event_into(&mut buf) {
1399 Ok(Event::Start(ref e)) => {
1400 match e.name().as_ref() {
1401 b"buildinfo" => {
1402 for attr in e.attributes().flatten() {
1404 match attr.key.as_ref() {
1405 b"build_id" => {
1406 scan_info.build_id =
1407 String::from_utf8_lossy(&attr.value).to_string()
1408 }
1409 b"analysis_unit" => {
1410 if scan_info.status == "Unknown" {
1412 scan_info.status =
1413 String::from_utf8_lossy(&attr.value).to_string();
1414 }
1415 }
1416 b"analysis_unit_id" => {
1417 scan_info.analysis_unit_id =
1418 Some(String::from_utf8_lossy(&attr.value).to_string())
1419 }
1420 b"scan_progress_percentage" => {
1421 if let Ok(progress_str) =
1422 String::from_utf8(attr.value.to_vec())
1423 {
1424 scan_info.scan_progress_percentage =
1425 progress_str.parse().ok();
1426 }
1427 }
1428 b"total_lines_of_code" => {
1429 if let Ok(lines_str) =
1430 String::from_utf8(attr.value.to_vec())
1431 {
1432 scan_info.total_lines_of_code = lines_str.parse().ok();
1433 }
1434 }
1435 _ => {}
1436 }
1437 }
1438 }
1439 b"build" => {
1440 inside_build = true;
1441 }
1442 b"analysis_unit" => {
1443 for attr in e.attributes().flatten() {
1445 match attr.key.as_ref() {
1446 b"status" => {
1447 scan_info.status =
1449 String::from_utf8_lossy(&attr.value).to_string();
1450 }
1451 b"analysis_type" => {
1452 scan_info.scan_type =
1453 String::from_utf8_lossy(&attr.value).to_string();
1454 }
1455 _ => {}
1456 }
1457 }
1458 }
1459 _ => {}
1460 }
1461 }
1462 Ok(Event::End(ref e)) => {
1463 if e.name().as_ref() == b"build" {
1464 inside_build = false;
1465 }
1466 }
1467 Ok(Event::Empty(ref e)) => {
1468 if e.name().as_ref() == b"analysis_unit" && inside_build {
1470 for attr in e.attributes().flatten() {
1471 match attr.key.as_ref() {
1472 b"status" => {
1473 scan_info.status =
1474 String::from_utf8_lossy(&attr.value).to_string();
1475 }
1476 b"analysis_type" => {
1477 scan_info.scan_type =
1478 String::from_utf8_lossy(&attr.value).to_string();
1479 }
1480 _ => {}
1481 }
1482 }
1483 }
1484 }
1485 Ok(Event::Eof) => break,
1486 Err(e) => {
1487 eprintln!("Error parsing XML: {e}");
1488 break;
1489 }
1490 _ => {}
1491 }
1492 buf.clear();
1493 }
1494
1495 Ok(scan_info)
1496 }
1497}
1498
1499impl ScanApi {
1501 pub async fn upload_file_to_sandbox(
1513 &self,
1514 app_id: &str,
1515 file_path: &str,
1516 sandbox_id: &str,
1517 ) -> Result<UploadedFile, ScanError> {
1518 let request = UploadFileRequest {
1519 app_id: app_id.to_string(),
1520 file_path: file_path.to_string(),
1521 save_as: None,
1522 sandbox_id: Some(sandbox_id.to_string()),
1523 };
1524
1525 self.upload_file(request).await
1526 }
1527
1528 pub async fn upload_file_to_app(
1539 &self,
1540 app_id: &str,
1541 file_path: &str,
1542 ) -> Result<UploadedFile, ScanError> {
1543 let request = UploadFileRequest {
1544 app_id: app_id.to_string(),
1545 file_path: file_path.to_string(),
1546 save_as: None,
1547 sandbox_id: None,
1548 };
1549
1550 self.upload_file(request).await
1551 }
1552
1553 pub async fn upload_large_file_to_sandbox(
1566 &self,
1567 app_id: &str,
1568 file_path: &str,
1569 sandbox_id: &str,
1570 filename: Option<&str>,
1571 ) -> Result<UploadedFile, ScanError> {
1572 let request = UploadLargeFileRequest {
1573 app_id: app_id.to_string(),
1574 file_path: file_path.to_string(),
1575 filename: filename.map(|s| s.to_string()),
1576 sandbox_id: Some(sandbox_id.to_string()),
1577 };
1578
1579 self.upload_large_file(request).await
1580 }
1581
1582 pub async fn upload_large_file_to_app(
1594 &self,
1595 app_id: &str,
1596 file_path: &str,
1597 filename: Option<&str>,
1598 ) -> Result<UploadedFile, ScanError> {
1599 let request = UploadLargeFileRequest {
1600 app_id: app_id.to_string(),
1601 file_path: file_path.to_string(),
1602 filename: filename.map(|s| s.to_string()),
1603 sandbox_id: None,
1604 };
1605
1606 self.upload_large_file(request).await
1607 }
1608
1609 pub async fn upload_large_file_to_sandbox_with_progress<F>(
1623 &self,
1624 app_id: &str,
1625 file_path: &str,
1626 sandbox_id: &str,
1627 filename: Option<&str>,
1628 progress_callback: F,
1629 ) -> Result<UploadedFile, ScanError>
1630 where
1631 F: Fn(u64, u64, f64) + Send + Sync,
1632 {
1633 let request = UploadLargeFileRequest {
1634 app_id: app_id.to_string(),
1635 file_path: file_path.to_string(),
1636 filename: filename.map(|s| s.to_string()),
1637 sandbox_id: Some(sandbox_id.to_string()),
1638 };
1639
1640 self.upload_large_file_with_progress(request, progress_callback)
1641 .await
1642 }
1643
1644 pub async fn begin_sandbox_prescan(
1655 &self,
1656 app_id: &str,
1657 sandbox_id: &str,
1658 ) -> Result<(), ScanError> {
1659 let request = BeginPreScanRequest {
1660 app_id: app_id.to_string(),
1661 sandbox_id: Some(sandbox_id.to_string()),
1662 auto_scan: Some(true),
1663 scan_all_nonfatal_top_level_modules: Some(true),
1664 include_new_modules: Some(true),
1665 };
1666
1667 self.begin_prescan(request).await
1668 }
1669
1670 pub async fn begin_sandbox_scan_all_modules(
1681 &self,
1682 app_id: &str,
1683 sandbox_id: &str,
1684 ) -> Result<(), ScanError> {
1685 let request = BeginScanRequest {
1686 app_id: app_id.to_string(),
1687 sandbox_id: Some(sandbox_id.to_string()),
1688 modules: None,
1689 scan_all_top_level_modules: Some(true),
1690 scan_all_nonfatal_top_level_modules: Some(true),
1691 scan_previously_selected_modules: None,
1692 };
1693
1694 self.begin_scan(request).await
1695 }
1696
1697 pub async fn upload_and_scan_sandbox(
1709 &self,
1710 app_id: &str,
1711 sandbox_id: &str,
1712 file_path: &str,
1713 ) -> Result<String, ScanError> {
1714 println!("📤 Uploading file to sandbox...");
1716 let _uploaded_file = self
1717 .upload_file_to_sandbox(app_id, file_path, sandbox_id)
1718 .await?;
1719
1720 println!("🔍 Beginning pre-scan...");
1722 self.begin_sandbox_prescan(app_id, sandbox_id).await?;
1723
1724 tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
1726
1727 println!("🚀 Beginning scan...");
1729 self.begin_sandbox_scan_all_modules(app_id, sandbox_id)
1730 .await?;
1731
1732 Ok("build_id_not_available".to_string())
1737 }
1738
1739 pub async fn delete_sandbox_build(
1751 &self,
1752 app_id: &str,
1753 build_id: &str,
1754 sandbox_id: &str,
1755 ) -> Result<(), ScanError> {
1756 self.delete_build(app_id, build_id, Some(sandbox_id)).await
1757 }
1758
1759 pub async fn delete_all_sandbox_builds(
1770 &self,
1771 app_id: &str,
1772 sandbox_id: &str,
1773 ) -> Result<(), ScanError> {
1774 self.delete_all_builds(app_id, Some(sandbox_id)).await
1775 }
1776
1777 pub async fn delete_app_build(&self, app_id: &str, build_id: &str) -> Result<(), ScanError> {
1788 self.delete_build(app_id, build_id, None).await
1789 }
1790
1791 pub async fn delete_all_app_builds(&self, app_id: &str) -> Result<(), ScanError> {
1801 self.delete_all_builds(app_id, None).await
1802 }
1803}
1804
1805#[cfg(test)]
1806mod tests {
1807 use super::*;
1808 use crate::VeracodeConfig;
1809
1810 #[test]
1811 fn test_upload_file_request() {
1812 let request = UploadFileRequest {
1813 app_id: "123".to_string(),
1814 file_path: "/path/to/file.jar".to_string(),
1815 save_as: Some("app.jar".to_string()),
1816 sandbox_id: Some("456".to_string()),
1817 };
1818
1819 assert_eq!(request.app_id, "123");
1820 assert_eq!(request.sandbox_id, Some("456".to_string()));
1821 }
1822
1823 #[test]
1824 fn test_begin_prescan_request() {
1825 let request = BeginPreScanRequest {
1826 app_id: "123".to_string(),
1827 sandbox_id: Some("456".to_string()),
1828 auto_scan: Some(true),
1829 scan_all_nonfatal_top_level_modules: Some(true),
1830 include_new_modules: Some(false),
1831 };
1832
1833 assert_eq!(request.app_id, "123");
1834 assert_eq!(request.auto_scan, Some(true));
1835 }
1836
1837 #[test]
1838 fn test_scan_error_display() {
1839 let error = ScanError::FileNotFound("test.jar".to_string());
1840 assert_eq!(error.to_string(), "File not found: test.jar");
1841
1842 let error = ScanError::UploadFailed("Network error".to_string());
1843 assert_eq!(error.to_string(), "Upload failed: Network error");
1844
1845 let error = ScanError::Unauthorized;
1846 assert_eq!(error.to_string(), "Unauthorized access");
1847
1848 let error = ScanError::BuildNotFound;
1849 assert_eq!(error.to_string(), "Build not found");
1850 }
1851
1852 #[test]
1853 fn test_delete_build_request_structure() {
1854 use crate::{VeracodeClient, VeracodeConfig};
1858
1859 async fn _test_delete_methods() -> Result<(), Box<dyn std::error::Error>> {
1860 let config = VeracodeConfig::new("test".to_string(), "test".to_string());
1861 let client = VeracodeClient::new(config)?;
1862 let api = client.scan_api();
1863
1864 let _: Result<(), _> = api
1867 .delete_build("app_id", "build_id", Some("sandbox_id"))
1868 .await;
1869 let _: Result<(), _> = api.delete_all_builds("app_id", Some("sandbox_id")).await;
1870 let _: Result<(), _> = api
1871 .delete_sandbox_build("app_id", "build_id", "sandbox_id")
1872 .await;
1873 let _: Result<(), _> = api.delete_all_sandbox_builds("app_id", "sandbox_id").await;
1874
1875 Ok(())
1876 }
1877
1878 }
1881
1882 #[test]
1883 fn test_upload_large_file_request() {
1884 let request = UploadLargeFileRequest {
1885 app_id: "123".to_string(),
1886 file_path: "/path/to/large_file.jar".to_string(),
1887 filename: Some("custom_name.jar".to_string()),
1888 sandbox_id: Some("456".to_string()),
1889 };
1890
1891 assert_eq!(request.app_id, "123");
1892 assert_eq!(request.filename, Some("custom_name.jar".to_string()));
1893 assert_eq!(request.sandbox_id, Some("456".to_string()));
1894 }
1895
1896 #[test]
1897 fn test_upload_progress() {
1898 let progress = UploadProgress {
1899 bytes_uploaded: 1024,
1900 total_bytes: 2048,
1901 percentage: 50.0,
1902 };
1903
1904 assert_eq!(progress.bytes_uploaded, 1024);
1905 assert_eq!(progress.total_bytes, 2048);
1906 assert_eq!(progress.percentage, 50.0);
1907 }
1908
1909 #[test]
1910 fn test_large_file_scan_error_display() {
1911 let error = ScanError::FileTooLarge("File exceeds 2GB".to_string());
1912 assert_eq!(error.to_string(), "File too large: File exceeds 2GB");
1913
1914 let error = ScanError::UploadInProgress;
1915 assert_eq!(error.to_string(), "Upload or prescan already in progress");
1916
1917 let error = ScanError::ScanInProgress;
1918 assert_eq!(error.to_string(), "Scan in progress, cannot upload");
1919
1920 let error = ScanError::ChunkedUploadFailed("Network error".to_string());
1921 assert_eq!(error.to_string(), "Chunked upload failed: Network error");
1922 }
1923
1924 #[tokio::test]
1925 async fn test_large_file_upload_method_signatures() {
1926 async fn _test_large_file_methods() -> Result<(), Box<dyn std::error::Error>> {
1927 let config = VeracodeConfig::new("test".to_string(), "test".to_string());
1928 let client = VeracodeClient::new(config)?;
1929 let api = client.scan_api();
1930
1931 let request = UploadLargeFileRequest {
1933 app_id: "123".to_string(),
1934 file_path: "/nonexistent/file.jar".to_string(),
1935 filename: None,
1936 sandbox_id: Some("456".to_string()),
1937 };
1938
1939 let _: Result<UploadedFile, _> = api.upload_large_file(request.clone()).await;
1942 let _: Result<UploadedFile, _> = api
1943 .upload_large_file_to_sandbox("123", "/path", "456", None)
1944 .await;
1945 let _: Result<UploadedFile, _> =
1946 api.upload_large_file_to_app("123", "/path", None).await;
1947
1948 let progress_callback = |bytes_uploaded: u64, total_bytes: u64, percentage: f64| {
1950 println!("Progress: {bytes_uploaded}/{total_bytes} ({percentage:.1}%)");
1951 };
1952 let _: Result<UploadedFile, _> = api
1953 .upload_large_file_with_progress(request, progress_callback)
1954 .await;
1955
1956 Ok(())
1957 }
1958
1959 }
1962}