1use chrono::{DateTime, Utc};
8#[allow(unused_imports)] use log::{debug, error, info};
10use quick_xml::Reader;
11use quick_xml::events::Event;
12use serde::{Deserialize, Serialize};
13use std::path::Path;
14
15use crate::{VeracodeClient, VeracodeError};
16
17fn attr_to_string(value: &[u8]) -> String {
20 String::from_utf8_lossy(value).into_owned()
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct UploadedFile {
26 pub file_id: String,
28 pub file_name: String,
30 pub file_size: u64,
32 pub uploaded: DateTime<Utc>,
34 pub file_status: String,
36 pub md5: Option<String>,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct PreScanResults {
43 pub build_id: String,
45 pub app_id: String,
47 pub sandbox_id: Option<String>,
49 pub status: String,
51 pub modules: Vec<ScanModule>,
53 pub messages: Vec<PreScanMessage>,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct ScanModule {
60 pub id: String,
62 pub name: String,
64 pub module_type: String,
66 pub is_fatal: bool,
68 pub selected: bool,
70 pub size: Option<u64>,
72 pub platform: Option<String>,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct PreScanMessage {
79 pub severity: String,
81 pub text: String,
83 pub module_name: Option<String>,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ScanInfo {
90 pub build_id: String,
92 pub app_id: String,
94 pub sandbox_id: Option<String>,
96 pub status: String,
98 pub scan_type: String,
100 pub analysis_unit_id: Option<String>,
102 pub scan_progress_percentage: Option<u32>,
104 pub scan_start: Option<DateTime<Utc>>,
106 pub scan_complete: Option<DateTime<Utc>>,
108 pub total_lines_of_code: Option<u64>,
110}
111
112#[derive(Debug, Clone)]
114pub struct UploadFileRequest {
115 pub app_id: String,
117 pub file_path: String,
119 pub save_as: Option<String>,
121 pub sandbox_id: Option<String>,
123}
124
125#[derive(Debug, Clone)]
127pub struct UploadLargeFileRequest {
128 pub app_id: String,
130 pub file_path: String,
132 pub filename: Option<String>,
134 pub sandbox_id: Option<String>,
136}
137
138#[derive(Debug, Clone)]
140pub struct UploadProgress {
141 pub bytes_uploaded: u64,
143 pub total_bytes: u64,
145 pub percentage: f64,
147}
148
149pub trait UploadProgressCallback: Send + Sync {
151 fn on_progress(&self, progress: UploadProgress);
153 fn on_completed(&self);
155 fn on_error(&self, error: &str);
157}
158
159#[derive(Debug, Clone)]
161pub struct BeginPreScanRequest {
162 pub app_id: String,
164 pub sandbox_id: Option<String>,
166 pub auto_scan: Option<bool>,
168 pub scan_all_nonfatal_top_level_modules: Option<bool>,
170 pub include_new_modules: Option<bool>,
172}
173
174#[derive(Debug, Clone)]
176pub struct BeginScanRequest {
177 pub app_id: String,
179 pub sandbox_id: Option<String>,
181 pub modules: Option<String>,
183 pub scan_all_top_level_modules: Option<bool>,
185 pub scan_all_nonfatal_top_level_modules: Option<bool>,
187 pub scan_previously_selected_modules: Option<bool>,
189}
190
191impl From<&UploadFileRequest> for UploadLargeFileRequest {
193 fn from(request: &UploadFileRequest) -> Self {
194 UploadLargeFileRequest {
195 app_id: request.app_id.clone(),
196 file_path: request.file_path.clone(),
197 filename: request.save_as.clone(),
198 sandbox_id: request.sandbox_id.clone(),
199 }
200 }
201}
202
203#[derive(Debug)]
205#[must_use = "Need to handle all error enum types."]
206pub enum ScanError {
207 Api(VeracodeError),
209 FileNotFound(String),
211 InvalidFileFormat(String),
213 UploadFailed(String),
215 ScanFailed(String),
217 PreScanFailed(String),
219 BuildNotFound,
221 ApplicationNotFound,
223 SandboxNotFound,
225 Unauthorized,
227 PermissionDenied,
229 InvalidParameter(String),
231 FileTooLarge(String),
233 UploadInProgress,
235 ScanInProgress,
237 BuildCreationFailed(String),
239 ChunkedUploadFailed(String),
241}
242
243impl std::fmt::Display for ScanError {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self {
246 ScanError::Api(err) => write!(f, "API error: {err}"),
247 ScanError::FileNotFound(path) => write!(f, "File not found: {path}"),
248 ScanError::InvalidFileFormat(msg) => write!(f, "Invalid file format: {msg}"),
249 ScanError::UploadFailed(msg) => write!(f, "Upload failed: {msg}"),
250 ScanError::ScanFailed(msg) => write!(f, "Scan failed: {msg}"),
251 ScanError::PreScanFailed(msg) => write!(f, "Pre-scan failed: {msg}"),
252 ScanError::BuildNotFound => write!(f, "Build not found"),
253 ScanError::ApplicationNotFound => write!(f, "Application not found"),
254 ScanError::SandboxNotFound => write!(f, "Sandbox not found"),
255 ScanError::Unauthorized => write!(f, "Unauthorized access"),
256 ScanError::PermissionDenied => write!(f, "Permission denied"),
257 ScanError::InvalidParameter(msg) => write!(f, "Invalid parameter: {msg}"),
258 ScanError::FileTooLarge(msg) => write!(f, "File too large: {msg}"),
259 ScanError::UploadInProgress => write!(f, "Upload or prescan already in progress"),
260 ScanError::ScanInProgress => write!(f, "Scan in progress, cannot upload"),
261 ScanError::BuildCreationFailed(msg) => write!(f, "Build creation failed: {msg}"),
262 ScanError::ChunkedUploadFailed(msg) => write!(f, "Chunked upload failed: {msg}"),
263 }
264 }
265}
266
267impl std::error::Error for ScanError {}
268
269impl From<VeracodeError> for ScanError {
270 fn from(err: VeracodeError) -> Self {
271 ScanError::Api(err)
272 }
273}
274
275impl From<reqwest::Error> for ScanError {
276 fn from(err: reqwest::Error) -> Self {
277 ScanError::Api(VeracodeError::Http(err))
278 }
279}
280
281impl From<serde_json::Error> for ScanError {
282 fn from(err: serde_json::Error) -> Self {
283 ScanError::Api(VeracodeError::Serialization(err))
284 }
285}
286
287impl From<std::io::Error> for ScanError {
288 fn from(err: std::io::Error) -> Self {
289 ScanError::FileNotFound(err.to_string())
290 }
291}
292
293pub struct ScanApi {
295 client: VeracodeClient,
296}
297
298impl ScanApi {
299 #[must_use]
306 pub fn new(client: VeracodeClient) -> Self {
307 Self { client }
308 }
309
310 pub async fn upload_file(
325 &self,
326 request: &UploadFileRequest,
327 ) -> Result<UploadedFile, ScanError> {
328 if !Path::new(&request.file_path).exists() {
330 return Err(ScanError::FileNotFound(request.file_path.clone()));
331 }
332
333 let endpoint = "/api/5.0/uploadfile.do";
334
335 let mut query_params = Vec::new();
337 query_params.push(("app_id", request.app_id.as_str()));
338
339 if let Some(sandbox_id) = &request.sandbox_id {
340 query_params.push(("sandbox_id", sandbox_id.as_str()));
341 }
342
343 if let Some(save_as) = &request.save_as {
344 query_params.push(("save_as", save_as.as_str()));
345 }
346
347 let file_data = tokio::fs::read(&request.file_path).await?;
349
350 let filename = Path::new(&request.file_path)
352 .file_name()
353 .and_then(|f| f.to_str())
354 .unwrap_or("file");
355
356 let response = self
357 .client
358 .upload_file_with_query_params(endpoint, &query_params, "file", filename, file_data)
359 .await?;
360
361 let status = response.status().as_u16();
362 match status {
363 200 => {
364 let response_text = response.text().await?;
365 self.parse_upload_response(&response_text, &request.file_path)
366 .await
367 }
368 400 => {
369 let error_text = response.text().await.unwrap_or_default();
370 Err(ScanError::InvalidParameter(error_text))
371 }
372 401 => Err(ScanError::Unauthorized),
373 403 => Err(ScanError::PermissionDenied),
374 404 => {
375 if request.sandbox_id.is_some() {
376 Err(ScanError::SandboxNotFound)
377 } else {
378 Err(ScanError::ApplicationNotFound)
379 }
380 }
381 _ => {
382 let error_text = response.text().await.unwrap_or_default();
383 Err(ScanError::UploadFailed(format!(
384 "HTTP {status}: {error_text}"
385 )))
386 }
387 }
388 }
389
390 pub async fn upload_large_file(
409 &self,
410 request: UploadLargeFileRequest,
411 ) -> Result<UploadedFile, ScanError> {
412 if !Path::new(&request.file_path).exists() {
414 return Err(ScanError::FileNotFound(request.file_path));
415 }
416
417 let file_metadata = tokio::fs::metadata(&request.file_path).await?;
419 let file_size = file_metadata.len();
420 const MAX_FILE_SIZE: u64 = 2 * 1024 * 1024 * 1024; if file_size > MAX_FILE_SIZE {
423 return Err(ScanError::FileTooLarge(format!(
424 "File size {file_size} bytes exceeds 2GB limit"
425 )));
426 }
427
428 let endpoint = "uploadlargefile.do"; let mut query_params = Vec::new();
432 query_params.push(("app_id", request.app_id.as_str()));
433
434 if let Some(sandbox_id) = &request.sandbox_id {
435 query_params.push(("sandbox_id", sandbox_id.as_str()));
436 }
437
438 if let Some(filename) = &request.filename {
439 query_params.push(("filename", filename.as_str()));
440 }
441
442 let file_data = tokio::fs::read(&request.file_path).await?;
444
445 let response = self
446 .client
447 .upload_file_binary(endpoint, &query_params, file_data, "binary/octet-stream")
448 .await?;
449
450 let status = response.status().as_u16();
451 match status {
452 200 => {
453 let response_text = response.text().await?;
454 self.parse_upload_response(&response_text, &request.file_path)
455 .await
456 }
457 400 => {
458 let error_text = response.text().await.unwrap_or_default();
459 if error_text.contains("upload or prescan in progress") {
460 Err(ScanError::UploadInProgress)
461 } else if error_text.contains("scan in progress") {
462 Err(ScanError::ScanInProgress)
463 } else {
464 Err(ScanError::InvalidParameter(error_text))
465 }
466 }
467 401 => Err(ScanError::Unauthorized),
468 403 => Err(ScanError::PermissionDenied),
469 404 => {
470 if request.sandbox_id.is_some() {
471 Err(ScanError::SandboxNotFound)
472 } else {
473 Err(ScanError::ApplicationNotFound)
474 }
475 }
476 413 => Err(ScanError::FileTooLarge(
477 "File size exceeds server limits".to_string(),
478 )),
479 _ => {
480 let error_text = response.text().await.unwrap_or_default();
481 Err(ScanError::UploadFailed(format!(
482 "HTTP {status}: {error_text}"
483 )))
484 }
485 }
486 }
487
488 pub async fn upload_large_file_with_progress<F>(
517 &self,
518 request: UploadLargeFileRequest,
519 progress_callback: F,
520 ) -> Result<UploadedFile, ScanError>
521 where
522 F: Fn(u64, u64, f64) + Send + Sync,
523 {
524 if !Path::new(&request.file_path).exists() {
526 return Err(ScanError::FileNotFound(request.file_path));
527 }
528
529 let file_metadata = tokio::fs::metadata(&request.file_path).await?;
531 let file_size = file_metadata.len();
532 const MAX_FILE_SIZE: u64 = 2 * 1024 * 1024 * 1024; if file_size > MAX_FILE_SIZE {
535 return Err(ScanError::FileTooLarge(format!(
536 "File size {file_size} bytes exceeds 2GB limit"
537 )));
538 }
539
540 let endpoint = "uploadlargefile.do";
541
542 let mut query_params = Vec::new();
544 query_params.push(("app_id", request.app_id.as_str()));
545
546 if let Some(sandbox_id) = &request.sandbox_id {
547 query_params.push(("sandbox_id", sandbox_id.as_str()));
548 }
549
550 if let Some(filename) = &request.filename {
551 query_params.push(("filename", filename.as_str()));
552 }
553
554 let response = self
555 .client
556 .upload_large_file_chunked(
557 endpoint,
558 &query_params,
559 &request.file_path,
560 Some("binary/octet-stream"),
561 Some(progress_callback),
562 )
563 .await?;
564
565 let status = response.status().as_u16();
566 match status {
567 200 => {
568 let response_text = response.text().await?;
569 self.parse_upload_response(&response_text, &request.file_path)
570 .await
571 }
572 400 => {
573 let error_text = response.text().await.unwrap_or_default();
574 if error_text.contains("upload or prescan in progress") {
575 Err(ScanError::UploadInProgress)
576 } else if error_text.contains("scan in progress") {
577 Err(ScanError::ScanInProgress)
578 } else {
579 Err(ScanError::InvalidParameter(error_text))
580 }
581 }
582 401 => Err(ScanError::Unauthorized),
583 403 => Err(ScanError::PermissionDenied),
584 404 => {
585 if request.sandbox_id.is_some() {
586 Err(ScanError::SandboxNotFound)
587 } else {
588 Err(ScanError::ApplicationNotFound)
589 }
590 }
591 413 => Err(ScanError::FileTooLarge(
592 "File size exceeds server limits".to_string(),
593 )),
594 _ => {
595 let error_text = response.text().await.unwrap_or_default();
596 Err(ScanError::ChunkedUploadFailed(format!(
597 "HTTP {status}: {error_text}"
598 )))
599 }
600 }
601 }
602
603 pub async fn upload_file_smart(
621 &self,
622 request: &UploadFileRequest,
623 ) -> Result<UploadedFile, ScanError> {
624 if !Path::new(&request.file_path).exists() {
626 return Err(ScanError::FileNotFound(request.file_path.clone()));
627 }
628
629 let file_metadata = tokio::fs::metadata(&request.file_path).await?;
631 let file_size = file_metadata.len();
632
633 const LARGE_FILE_THRESHOLD: u64 = 100 * 1024 * 1024; if file_size > LARGE_FILE_THRESHOLD {
637 let large_request = UploadLargeFileRequest::from(request);
639
640 match self.upload_large_file(large_request).await {
642 Ok(result) => Ok(result),
643 Err(ScanError::Api(_)) => {
644 self.upload_file(request).await
646 }
647 Err(e) => Err(e),
648 }
649 } else {
650 self.upload_file(request).await
652 }
653 }
654
655 pub async fn begin_prescan(&self, request: &BeginPreScanRequest) -> Result<(), ScanError> {
670 let endpoint = "/api/5.0/beginprescan.do";
671
672 let mut query_params = Vec::new();
674 query_params.push(("app_id", request.app_id.as_str()));
675
676 if let Some(sandbox_id) = &request.sandbox_id {
677 query_params.push(("sandbox_id", sandbox_id.as_str()));
678 }
679
680 if let Some(auto_scan) = request.auto_scan {
681 query_params.push(("auto_scan", if auto_scan { "true" } else { "false" }));
682 }
683
684 if let Some(scan_all) = request.scan_all_nonfatal_top_level_modules {
685 query_params.push((
686 "scan_all_nonfatal_top_level_modules",
687 if scan_all { "true" } else { "false" },
688 ));
689 }
690
691 if let Some(include_new) = request.include_new_modules {
692 query_params.push((
693 "include_new_modules",
694 if include_new { "true" } else { "false" },
695 ));
696 }
697
698 let response = self.client.get_with_params(endpoint, &query_params).await?;
699
700 let status = response.status().as_u16();
701 match status {
702 200 => {
703 let response_text = response.text().await?;
704 self.validate_scan_response(&response_text)?;
707 Ok(())
708 }
709 400 => {
710 let error_text = response.text().await.unwrap_or_default();
711 Err(ScanError::InvalidParameter(error_text))
712 }
713 401 => Err(ScanError::Unauthorized),
714 403 => Err(ScanError::PermissionDenied),
715 404 => {
716 if request.sandbox_id.is_some() {
717 Err(ScanError::SandboxNotFound)
718 } else {
719 Err(ScanError::ApplicationNotFound)
720 }
721 }
722 _ => {
723 let error_text = response.text().await.unwrap_or_default();
724 Err(ScanError::PreScanFailed(format!(
725 "HTTP {status}: {error_text}"
726 )))
727 }
728 }
729 }
730
731 pub async fn get_prescan_results(
748 &self,
749 app_id: &str,
750 sandbox_id: Option<&str>,
751 build_id: Option<&str>,
752 ) -> Result<PreScanResults, ScanError> {
753 let endpoint = "/api/5.0/getprescanresults.do";
754
755 let mut params = Vec::new();
756 params.push(("app_id", app_id));
757
758 if let Some(sandbox_id) = sandbox_id {
759 params.push(("sandbox_id", sandbox_id));
760 }
761
762 if let Some(build_id) = build_id {
763 params.push(("build_id", build_id));
764 }
765
766 let response = self.client.get_with_params(endpoint, ¶ms).await?;
767
768 let status = response.status().as_u16();
769 match status {
770 200 => {
771 let response_text = response.text().await?;
772 self.parse_prescan_results(&response_text, app_id, sandbox_id)
773 }
774 401 => Err(ScanError::Unauthorized),
775 403 => Err(ScanError::PermissionDenied),
776 404 => {
777 if sandbox_id.is_some() {
778 Err(ScanError::SandboxNotFound)
779 } else {
780 Err(ScanError::ApplicationNotFound)
781 }
782 }
783 _ => {
784 let error_text = response.text().await.unwrap_or_default();
785 Err(ScanError::PreScanFailed(format!(
786 "HTTP {status}: {error_text}"
787 )))
788 }
789 }
790 }
791
792 pub async fn begin_scan(&self, request: &BeginScanRequest) -> Result<(), ScanError> {
807 let endpoint = "/api/5.0/beginscan.do";
808
809 let mut query_params = Vec::new();
811 query_params.push(("app_id", request.app_id.as_str()));
812
813 if let Some(sandbox_id) = &request.sandbox_id {
814 query_params.push(("sandbox_id", sandbox_id.as_str()));
815 }
816
817 if let Some(modules) = &request.modules {
818 query_params.push(("modules", modules.as_str()));
819 }
820
821 if let Some(scan_all) = request.scan_all_top_level_modules {
822 query_params.push((
823 "scan_all_top_level_modules",
824 if scan_all { "true" } else { "false" },
825 ));
826 }
827
828 if let Some(scan_all_nonfatal) = request.scan_all_nonfatal_top_level_modules {
829 query_params.push((
830 "scan_all_nonfatal_top_level_modules",
831 if scan_all_nonfatal { "true" } else { "false" },
832 ));
833 }
834
835 if let Some(scan_previous) = request.scan_previously_selected_modules {
836 query_params.push((
837 "scan_previously_selected_modules",
838 if scan_previous { "true" } else { "false" },
839 ));
840 }
841
842 let response = self.client.get_with_params(endpoint, &query_params).await?;
843
844 let status = response.status().as_u16();
845 match status {
846 200 => {
847 let response_text = response.text().await?;
848 self.validate_scan_response(&response_text)?;
851 Ok(())
852 }
853 400 => {
854 let error_text = response.text().await.unwrap_or_default();
855 Err(ScanError::InvalidParameter(error_text))
856 }
857 401 => Err(ScanError::Unauthorized),
858 403 => Err(ScanError::PermissionDenied),
859 404 => {
860 if request.sandbox_id.is_some() {
861 Err(ScanError::SandboxNotFound)
862 } else {
863 Err(ScanError::ApplicationNotFound)
864 }
865 }
866 _ => {
867 let error_text = response.text().await.unwrap_or_default();
868 Err(ScanError::ScanFailed(format!(
869 "HTTP {status}: {error_text}"
870 )))
871 }
872 }
873 }
874
875 pub async fn get_file_list(
892 &self,
893 app_id: &str,
894 sandbox_id: Option<&str>,
895 build_id: Option<&str>,
896 ) -> Result<Vec<UploadedFile>, ScanError> {
897 let endpoint = "/api/5.0/getfilelist.do";
898
899 let mut params = Vec::new();
900 params.push(("app_id", app_id));
901
902 if let Some(sandbox_id) = sandbox_id {
903 params.push(("sandbox_id", sandbox_id));
904 }
905
906 if let Some(build_id) = build_id {
907 params.push(("build_id", build_id));
908 }
909
910 let response = self.client.get_with_params(endpoint, ¶ms).await?;
911
912 let status = response.status().as_u16();
913 match status {
914 200 => {
915 let response_text = response.text().await?;
916 self.parse_file_list(&response_text)
917 }
918 401 => Err(ScanError::Unauthorized),
919 403 => Err(ScanError::PermissionDenied),
920 404 => {
921 if sandbox_id.is_some() {
922 Err(ScanError::SandboxNotFound)
923 } else {
924 Err(ScanError::ApplicationNotFound)
925 }
926 }
927 _ => {
928 let error_text = response.text().await.unwrap_or_default();
929 Err(ScanError::Api(VeracodeError::InvalidResponse(format!(
930 "HTTP {status}: {error_text}"
931 ))))
932 }
933 }
934 }
935
936 pub async fn remove_file(
953 &self,
954 app_id: &str,
955 file_id: &str,
956 sandbox_id: Option<&str>,
957 ) -> Result<(), ScanError> {
958 let endpoint = "/api/5.0/removefile.do";
959
960 let mut query_params = Vec::new();
962 query_params.push(("app_id", app_id));
963 query_params.push(("file_id", file_id));
964
965 if let Some(sandbox_id) = sandbox_id {
966 query_params.push(("sandbox_id", sandbox_id));
967 }
968
969 let response = self.client.get_with_params(endpoint, &query_params).await?;
970
971 let status = response.status().as_u16();
972 match status {
973 200 => Ok(()),
974 400 => {
975 let error_text = response.text().await.unwrap_or_default();
976 Err(ScanError::InvalidParameter(error_text))
977 }
978 401 => Err(ScanError::Unauthorized),
979 403 => Err(ScanError::PermissionDenied),
980 404 => Err(ScanError::FileNotFound(file_id.to_string())),
981 _ => {
982 let error_text = response.text().await.unwrap_or_default();
983 Err(ScanError::Api(VeracodeError::InvalidResponse(format!(
984 "HTTP {status}: {error_text}"
985 ))))
986 }
987 }
988 }
989
990 pub async fn delete_build(
1009 &self,
1010 app_id: &str,
1011 build_id: &str,
1012 sandbox_id: Option<&str>,
1013 ) -> Result<(), ScanError> {
1014 let endpoint = "/api/5.0/deletebuild.do";
1015
1016 let mut query_params = Vec::new();
1018 query_params.push(("app_id", app_id));
1019 query_params.push(("build_id", build_id));
1020
1021 if let Some(sandbox_id) = sandbox_id {
1022 query_params.push(("sandbox_id", sandbox_id));
1023 }
1024
1025 let response = self.client.get_with_params(endpoint, &query_params).await?;
1026
1027 let status = response.status().as_u16();
1028 match status {
1029 200 => Ok(()),
1030 400 => {
1031 let error_text = response.text().await.unwrap_or_default();
1032 Err(ScanError::InvalidParameter(error_text))
1033 }
1034 401 => Err(ScanError::Unauthorized),
1035 403 => Err(ScanError::PermissionDenied),
1036 404 => Err(ScanError::BuildNotFound),
1037 _ => {
1038 let error_text = response.text().await.unwrap_or_default();
1039 Err(ScanError::Api(VeracodeError::InvalidResponse(format!(
1040 "HTTP {status}: {error_text}"
1041 ))))
1042 }
1043 }
1044 }
1045
1046 pub async fn delete_all_builds(
1065 &self,
1066 app_id: &str,
1067 sandbox_id: Option<&str>,
1068 ) -> Result<(), ScanError> {
1069 let build_info = self.get_build_info(app_id, None, sandbox_id).await?;
1071
1072 if !build_info.build_id.is_empty() && build_info.build_id != "unknown" {
1073 info!("Deleting build: {}", build_info.build_id);
1074 self.delete_build(app_id, &build_info.build_id, sandbox_id)
1075 .await?;
1076 }
1077
1078 Ok(())
1079 }
1080
1081 pub async fn get_build_info(
1098 &self,
1099 app_id: &str,
1100 build_id: Option<&str>,
1101 sandbox_id: Option<&str>,
1102 ) -> Result<ScanInfo, ScanError> {
1103 let endpoint = "/api/5.0/getbuildinfo.do";
1104
1105 let mut params = Vec::new();
1106 params.push(("app_id", app_id));
1107
1108 if let Some(build_id) = build_id {
1109 params.push(("build_id", build_id));
1110 }
1111
1112 if let Some(sandbox_id) = sandbox_id {
1113 params.push(("sandbox_id", sandbox_id));
1114 }
1115
1116 let response = self.client.get_with_params(endpoint, ¶ms).await?;
1117
1118 let status = response.status().as_u16();
1119 match status {
1120 200 => {
1121 let response_text = response.text().await?;
1122 self.parse_build_info(&response_text, app_id, sandbox_id)
1123 }
1124 401 => Err(ScanError::Unauthorized),
1125 403 => Err(ScanError::PermissionDenied),
1126 404 => Err(ScanError::BuildNotFound),
1127 _ => {
1128 let error_text = response.text().await.unwrap_or_default();
1129 Err(ScanError::Api(VeracodeError::InvalidResponse(format!(
1130 "HTTP {status}: {error_text}"
1131 ))))
1132 }
1133 }
1134 }
1135
1136 async fn parse_upload_response(
1139 &self,
1140 xml: &str,
1141 file_path: &str,
1142 ) -> Result<UploadedFile, ScanError> {
1143 let mut reader = Reader::from_str(xml);
1144 reader.config_mut().trim_text(true);
1145
1146 let mut buf = Vec::new();
1147 let mut file_id = None;
1148 let mut file_status = "Unknown".to_string();
1149 let mut _md5: Option<String> = None;
1150
1151 loop {
1152 match reader.read_event_into(&mut buf) {
1153 Ok(Event::Start(ref e)) => {
1154 if e.name().as_ref() == b"file" {
1155 for attr in e.attributes().flatten() {
1157 if attr.key.as_ref() == b"file_id" {
1158 file_id = Some(attr_to_string(&attr.value));
1159 }
1160 }
1161 }
1162 }
1163 Ok(Event::Text(e)) => {
1164 let text = std::str::from_utf8(&e).unwrap_or_default();
1165 if text.contains("successfully uploaded") {
1167 file_status = "Uploaded".to_string();
1168 } else if text.contains("error") || text.contains("failed") {
1169 file_status = "Failed".to_string();
1170 }
1171 }
1172 Ok(Event::Eof) => break,
1173 Err(e) => {
1174 error!("Error parsing XML: {e}");
1175 break;
1176 }
1177 _ => {}
1178 }
1179 buf.clear();
1180 }
1181
1182 let filename = Path::new(file_path)
1183 .file_name()
1184 .and_then(|f| f.to_str())
1185 .unwrap_or("file")
1186 .to_string();
1187
1188 Ok(UploadedFile {
1189 file_id: file_id.unwrap_or_else(|| format!("file_{}", chrono::Utc::now().timestamp())),
1190 file_name: filename,
1191 file_size: tokio::fs::metadata(file_path)
1192 .await
1193 .map(|m| m.len())
1194 .unwrap_or(0),
1195 uploaded: Utc::now(),
1196 file_status,
1197 md5: None,
1198 })
1199 }
1200
1201 fn validate_scan_response(&self, xml: &str) -> Result<(), ScanError> {
1208 if xml.contains("<error>") {
1210 let mut reader = Reader::from_str(xml);
1212 reader.config_mut().trim_text(true);
1213
1214 let mut buf = Vec::new();
1215 let mut in_error = false;
1216 let mut error_message = String::new();
1217
1218 loop {
1219 match reader.read_event_into(&mut buf) {
1220 Ok(Event::Start(ref e)) if e.name().as_ref() == b"error" => {
1221 in_error = true;
1222 }
1223 Ok(Event::Text(ref e)) if in_error => {
1224 error_message.push_str(&String::from_utf8_lossy(e));
1225 }
1226 Ok(Event::End(ref e)) if e.name().as_ref() == b"error" => {
1227 break;
1228 }
1229 Ok(Event::Eof) => break,
1230 Err(e) => {
1231 return Err(ScanError::ScanFailed(format!("XML parsing error: {e}")));
1232 }
1233 _ => {}
1234 }
1235 buf.clear();
1236 }
1237
1238 if !error_message.is_empty() {
1239 return Err(ScanError::ScanFailed(error_message));
1240 }
1241 return Err(ScanError::ScanFailed(
1242 "Unknown error in scan response".to_string(),
1243 ));
1244 }
1245
1246 if xml.contains("<buildinfo") || xml.contains("<build") {
1248 Ok(())
1249 } else {
1250 Err(ScanError::ScanFailed(
1251 "Invalid scan response format".to_string(),
1252 ))
1253 }
1254 }
1255
1256 fn parse_module_from_attributes<'a>(
1258 &self,
1259 attributes: impl Iterator<
1260 Item = Result<
1261 quick_xml::events::attributes::Attribute<'a>,
1262 quick_xml::events::attributes::AttrError,
1263 >,
1264 >,
1265 has_fatal_errors: &mut bool,
1266 ) -> ScanModule {
1267 let mut module = ScanModule {
1268 id: String::new(),
1269 name: String::new(),
1270 module_type: String::new(),
1271 is_fatal: false,
1272 selected: false,
1273 size: None,
1274 platform: None,
1275 };
1276
1277 for attr in attributes.flatten() {
1278 match attr.key.as_ref() {
1279 b"id" => module.id = attr_to_string(&attr.value),
1280 b"name" => module.name = attr_to_string(&attr.value),
1281 b"type" => module.module_type = attr_to_string(&attr.value),
1282 b"isfatal" => module.is_fatal = attr.value.as_ref() == b"true",
1283 b"selected" => module.selected = attr.value.as_ref() == b"true",
1284 b"has_fatal_errors" => {
1285 if attr.value.as_ref() == b"true" {
1286 *has_fatal_errors = true;
1287 }
1288 }
1289 b"size" => {
1290 if let Ok(size_str) = String::from_utf8(attr.value.to_vec()) {
1291 module.size = size_str.parse().ok();
1292 }
1293 }
1294 b"platform" => module.platform = Some(attr_to_string(&attr.value)),
1295 _ => {}
1296 }
1297 }
1298
1299 module
1300 }
1301
1302 fn parse_prescan_results(
1303 &self,
1304 xml: &str,
1305 app_id: &str,
1306 sandbox_id: Option<&str>,
1307 ) -> Result<PreScanResults, ScanError> {
1308 if xml.contains("<error>") && xml.contains("Prescan results not available") {
1310 return Ok(PreScanResults {
1312 build_id: String::new(),
1313 app_id: app_id.to_string(),
1314 sandbox_id: sandbox_id.map(|s| s.to_string()),
1315 status: "Pre-Scan Submitted".to_string(), modules: Vec::new(),
1317 messages: Vec::new(),
1318 });
1319 }
1320
1321 let mut reader = Reader::from_str(xml);
1322 reader.config_mut().trim_text(true);
1323
1324 let mut buf = Vec::new();
1325 let mut build_id = None;
1326 let mut modules = Vec::new();
1327 let messages = Vec::new();
1328 let mut has_prescan_results = false;
1329 let mut has_fatal_errors = false;
1330
1331 loop {
1332 match reader.read_event_into(&mut buf) {
1333 Ok(Event::Start(ref e)) => {
1334 match e.name().as_ref() {
1335 b"prescanresults" => {
1336 has_prescan_results = true;
1337 for attr in e.attributes().flatten() {
1339 if attr.key.as_ref() == b"build_id" {
1340 build_id = Some(attr_to_string(&attr.value));
1341 }
1342 }
1343 }
1344 b"module" => {
1345 let module = self.parse_module_from_attributes(
1346 e.attributes(),
1347 &mut has_fatal_errors,
1348 );
1349 modules.push(module);
1350 }
1351 _ => {}
1352 }
1353 }
1354 Ok(Event::Empty(ref e)) => {
1355 if e.name().as_ref() == b"module" {
1357 let module = self
1358 .parse_module_from_attributes(e.attributes(), &mut has_fatal_errors);
1359 modules.push(module);
1360 }
1361 }
1362 Ok(Event::Eof) => break,
1363 Err(e) => {
1364 error!("Error parsing XML: {e}");
1365 break;
1366 }
1367 _ => {}
1368 }
1369 buf.clear();
1370 }
1371
1372 let status = if !has_prescan_results {
1374 "Unknown".to_string()
1375 } else if modules.is_empty() {
1376 "Pre-Scan Failed".to_string()
1378 } else if has_fatal_errors {
1379 "Pre-Scan Failed".to_string()
1381 } else {
1382 "Pre-Scan Success".to_string()
1384 };
1385
1386 Ok(PreScanResults {
1387 build_id: build_id.unwrap_or_else(|| "unknown".to_string()),
1388 app_id: app_id.to_string(),
1389 sandbox_id: sandbox_id.map(|s| s.to_string()),
1390 status,
1391 modules,
1392 messages,
1393 })
1394 }
1395
1396 fn parse_file_from_attributes<'a>(
1398 &self,
1399 attributes: impl Iterator<
1400 Item = Result<
1401 quick_xml::events::attributes::Attribute<'a>,
1402 quick_xml::events::attributes::AttrError,
1403 >,
1404 >,
1405 ) -> UploadedFile {
1406 let mut file = UploadedFile {
1407 file_id: String::new(),
1408 file_name: String::new(),
1409 file_size: 0,
1410 uploaded: Utc::now(),
1411 file_status: "Unknown".to_string(),
1412 md5: None,
1413 };
1414
1415 for attr in attributes.flatten() {
1416 match attr.key.as_ref() {
1417 b"file_id" => file.file_id = attr_to_string(&attr.value),
1418 b"file_name" => file.file_name = attr_to_string(&attr.value),
1419 b"file_size" => {
1420 if let Ok(size_str) = String::from_utf8(attr.value.to_vec()) {
1421 file.file_size = size_str.parse().unwrap_or(0);
1422 }
1423 }
1424 b"file_status" => file.file_status = attr_to_string(&attr.value),
1425 b"md5" => file.md5 = Some(String::from_utf8_lossy(&attr.value).to_string()),
1426 _ => {}
1427 }
1428 }
1429
1430 file
1431 }
1432
1433 fn parse_file_list(&self, xml: &str) -> Result<Vec<UploadedFile>, ScanError> {
1434 let mut reader = Reader::from_str(xml);
1435 reader.config_mut().trim_text(true);
1436
1437 let mut buf = Vec::new();
1438 let mut files = Vec::new();
1439
1440 loop {
1441 match reader.read_event_into(&mut buf) {
1442 Ok(Event::Start(ref e)) => {
1443 if e.name().as_ref() == b"file" {
1444 let file = self.parse_file_from_attributes(e.attributes());
1445 files.push(file);
1446 }
1447 }
1448 Ok(Event::Empty(ref e)) => {
1449 if e.name().as_ref() == b"file" {
1451 let file = self.parse_file_from_attributes(e.attributes());
1452 files.push(file);
1453 }
1454 }
1455 Ok(Event::Eof) => break,
1456 Err(e) => {
1457 error!("Error parsing XML: {e}");
1458 break;
1459 }
1460 _ => {}
1461 }
1462 buf.clear();
1463 }
1464
1465 Ok(files)
1466 }
1467
1468 fn parse_build_info(
1469 &self,
1470 xml: &str,
1471 app_id: &str,
1472 sandbox_id: Option<&str>,
1473 ) -> Result<ScanInfo, ScanError> {
1474 let mut reader = Reader::from_str(xml);
1475 reader.config_mut().trim_text(true);
1476
1477 let mut buf = Vec::new();
1478 let mut scan_info = ScanInfo {
1479 build_id: String::new(),
1480 app_id: app_id.to_string(),
1481 sandbox_id: sandbox_id.map(|s| s.to_string()),
1482 status: "Unknown".to_string(),
1483 scan_type: "Static".to_string(),
1484 analysis_unit_id: None,
1485 scan_progress_percentage: None,
1486 scan_start: None,
1487 scan_complete: None,
1488 total_lines_of_code: None,
1489 };
1490
1491 let mut inside_build = false;
1492
1493 loop {
1494 match reader.read_event_into(&mut buf) {
1495 Ok(Event::Start(ref e)) => {
1496 match e.name().as_ref() {
1497 b"buildinfo" => {
1498 for attr in e.attributes().flatten() {
1500 match attr.key.as_ref() {
1501 b"build_id" => scan_info.build_id = attr_to_string(&attr.value),
1502 b"analysis_unit" => {
1503 if scan_info.status == "Unknown" {
1505 scan_info.status = attr_to_string(&attr.value);
1506 }
1507 }
1508 b"analysis_unit_id" => {
1509 scan_info.analysis_unit_id =
1510 Some(attr_to_string(&attr.value))
1511 }
1512 b"scan_progress_percentage" => {
1513 if let Ok(progress_str) =
1514 String::from_utf8(attr.value.to_vec())
1515 {
1516 scan_info.scan_progress_percentage =
1517 progress_str.parse().ok();
1518 }
1519 }
1520 b"total_lines_of_code" => {
1521 if let Ok(lines_str) =
1522 String::from_utf8(attr.value.to_vec())
1523 {
1524 scan_info.total_lines_of_code = lines_str.parse().ok();
1525 }
1526 }
1527 _ => {}
1528 }
1529 }
1530 }
1531 b"build" => {
1532 inside_build = true;
1533 }
1534 b"analysis_unit" => {
1535 for attr in e.attributes().flatten() {
1537 match attr.key.as_ref() {
1538 b"status" => {
1539 scan_info.status = attr_to_string(&attr.value);
1541 }
1542 b"analysis_type" => {
1543 scan_info.scan_type = attr_to_string(&attr.value);
1544 }
1545 _ => {}
1546 }
1547 }
1548 }
1549 _ => {}
1550 }
1551 }
1552 Ok(Event::End(ref e)) => {
1553 if e.name().as_ref() == b"build" {
1554 inside_build = false;
1555 }
1556 }
1557 Ok(Event::Empty(ref e)) => {
1558 if e.name().as_ref() == b"analysis_unit" && inside_build {
1560 for attr in e.attributes().flatten() {
1561 match attr.key.as_ref() {
1562 b"status" => {
1563 scan_info.status = attr_to_string(&attr.value);
1564 }
1565 b"analysis_type" => {
1566 scan_info.scan_type = attr_to_string(&attr.value);
1567 }
1568 _ => {}
1569 }
1570 }
1571 }
1572 }
1573 Ok(Event::Eof) => break,
1574 Err(e) => {
1575 error!("Error parsing XML: {e}");
1576 break;
1577 }
1578 _ => {}
1579 }
1580 buf.clear();
1581 }
1582
1583 Ok(scan_info)
1584 }
1585}
1586
1587impl ScanApi {
1589 pub async fn upload_file_to_sandbox(
1606 &self,
1607 app_id: &str,
1608 file_path: &str,
1609 sandbox_id: &str,
1610 ) -> Result<UploadedFile, ScanError> {
1611 let request = UploadFileRequest {
1612 app_id: app_id.to_string(),
1613 file_path: file_path.to_string(),
1614 save_as: None,
1615 sandbox_id: Some(sandbox_id.to_string()),
1616 };
1617
1618 self.upload_file(&request).await
1619 }
1620
1621 pub async fn upload_file_to_app(
1637 &self,
1638 app_id: &str,
1639 file_path: &str,
1640 ) -> Result<UploadedFile, ScanError> {
1641 let request = UploadFileRequest {
1642 app_id: app_id.to_string(),
1643 file_path: file_path.to_string(),
1644 save_as: None,
1645 sandbox_id: None,
1646 };
1647
1648 self.upload_file(&request).await
1649 }
1650
1651 pub async fn upload_large_file_to_sandbox(
1669 &self,
1670 app_id: &str,
1671 file_path: &str,
1672 sandbox_id: &str,
1673 filename: Option<&str>,
1674 ) -> Result<UploadedFile, ScanError> {
1675 let request = UploadLargeFileRequest {
1676 app_id: app_id.to_string(),
1677 file_path: file_path.to_string(),
1678 filename: filename.map(|s| s.to_string()),
1679 sandbox_id: Some(sandbox_id.to_string()),
1680 };
1681
1682 self.upload_large_file(request).await
1683 }
1684
1685 pub async fn upload_large_file_to_app(
1702 &self,
1703 app_id: &str,
1704 file_path: &str,
1705 filename: Option<&str>,
1706 ) -> Result<UploadedFile, ScanError> {
1707 let request = UploadLargeFileRequest {
1708 app_id: app_id.to_string(),
1709 file_path: file_path.to_string(),
1710 filename: filename.map(|s| s.to_string()),
1711 sandbox_id: None,
1712 };
1713
1714 self.upload_large_file(request).await
1715 }
1716
1717 pub async fn upload_large_file_to_sandbox_with_progress<F>(
1736 &self,
1737 app_id: &str,
1738 file_path: &str,
1739 sandbox_id: &str,
1740 filename: Option<&str>,
1741 progress_callback: F,
1742 ) -> Result<UploadedFile, ScanError>
1743 where
1744 F: Fn(u64, u64, f64) + Send + Sync,
1745 {
1746 let request = UploadLargeFileRequest {
1747 app_id: app_id.to_string(),
1748 file_path: file_path.to_string(),
1749 filename: filename.map(|s| s.to_string()),
1750 sandbox_id: Some(sandbox_id.to_string()),
1751 };
1752
1753 self.upload_large_file_with_progress(request, progress_callback)
1754 .await
1755 }
1756
1757 pub async fn begin_sandbox_prescan(
1773 &self,
1774 app_id: &str,
1775 sandbox_id: &str,
1776 ) -> Result<(), ScanError> {
1777 let request = BeginPreScanRequest {
1778 app_id: app_id.to_string(),
1779 sandbox_id: Some(sandbox_id.to_string()),
1780 auto_scan: Some(true),
1781 scan_all_nonfatal_top_level_modules: Some(true),
1782 include_new_modules: Some(true),
1783 };
1784
1785 self.begin_prescan(&request).await
1786 }
1787
1788 pub async fn begin_sandbox_scan_all_modules(
1804 &self,
1805 app_id: &str,
1806 sandbox_id: &str,
1807 ) -> Result<(), ScanError> {
1808 let request = BeginScanRequest {
1809 app_id: app_id.to_string(),
1810 sandbox_id: Some(sandbox_id.to_string()),
1811 modules: None,
1812 scan_all_top_level_modules: Some(true),
1813 scan_all_nonfatal_top_level_modules: Some(true),
1814 scan_previously_selected_modules: None,
1815 };
1816
1817 self.begin_scan(&request).await
1818 }
1819
1820 pub async fn upload_and_scan_sandbox(
1837 &self,
1838 app_id: &str,
1839 sandbox_id: &str,
1840 file_path: &str,
1841 ) -> Result<String, ScanError> {
1842 info!("Uploading file to sandbox...");
1844 let _uploaded_file = self
1845 .upload_file_to_sandbox(app_id, file_path, sandbox_id)
1846 .await?;
1847
1848 info!("Beginning pre-scan...");
1850 self.begin_sandbox_prescan(app_id, sandbox_id).await?;
1851
1852 tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
1854
1855 info!("Beginning scan...");
1857 self.begin_sandbox_scan_all_modules(app_id, sandbox_id)
1858 .await?;
1859
1860 Ok("build_id_not_available".to_string())
1865 }
1866
1867 pub async fn delete_sandbox_build(
1884 &self,
1885 app_id: &str,
1886 build_id: &str,
1887 sandbox_id: &str,
1888 ) -> Result<(), ScanError> {
1889 self.delete_build(app_id, build_id, Some(sandbox_id)).await
1890 }
1891
1892 pub async fn delete_all_sandbox_builds(
1908 &self,
1909 app_id: &str,
1910 sandbox_id: &str,
1911 ) -> Result<(), ScanError> {
1912 self.delete_all_builds(app_id, Some(sandbox_id)).await
1913 }
1914
1915 pub async fn delete_app_build(&self, app_id: &str, build_id: &str) -> Result<(), ScanError> {
1931 self.delete_build(app_id, build_id, None).await
1932 }
1933
1934 pub async fn delete_all_app_builds(&self, app_id: &str) -> Result<(), ScanError> {
1949 self.delete_all_builds(app_id, None).await
1950 }
1951}
1952
1953#[cfg(test)]
1954mod tests {
1955 use super::*;
1956 use crate::VeracodeConfig;
1957
1958 #[test]
1959 fn test_upload_file_request() {
1960 let request = UploadFileRequest {
1961 app_id: "123".to_string(),
1962 file_path: "/path/to/file.jar".to_string(),
1963 save_as: Some("app.jar".to_string()),
1964 sandbox_id: Some("456".to_string()),
1965 };
1966
1967 assert_eq!(request.app_id, "123");
1968 assert_eq!(request.sandbox_id, Some("456".to_string()));
1969 }
1970
1971 #[test]
1972 fn test_begin_prescan_request() {
1973 let request = BeginPreScanRequest {
1974 app_id: "123".to_string(),
1975 sandbox_id: Some("456".to_string()),
1976 auto_scan: Some(true),
1977 scan_all_nonfatal_top_level_modules: Some(true),
1978 include_new_modules: Some(false),
1979 };
1980
1981 assert_eq!(request.app_id, "123");
1982 assert_eq!(request.auto_scan, Some(true));
1983 }
1984
1985 #[test]
1986 fn test_scan_error_display() {
1987 let error = ScanError::FileNotFound("test.jar".to_string());
1988 assert_eq!(error.to_string(), "File not found: test.jar");
1989
1990 let error = ScanError::UploadFailed("Network error".to_string());
1991 assert_eq!(error.to_string(), "Upload failed: Network error");
1992
1993 let error = ScanError::Unauthorized;
1994 assert_eq!(error.to_string(), "Unauthorized access");
1995
1996 let error = ScanError::BuildNotFound;
1997 assert_eq!(error.to_string(), "Build not found");
1998 }
1999
2000 #[test]
2001 fn test_delete_build_request_structure() {
2002 use crate::{VeracodeClient, VeracodeConfig};
2006
2007 async fn _test_delete_methods() -> Result<(), Box<dyn std::error::Error>> {
2008 let config = VeracodeConfig::new("test", "test");
2009 let client = VeracodeClient::new(config)?;
2010 let api = client.scan_api()?;
2011
2012 let _: Result<(), _> = api
2015 .delete_build("app_id", "build_id", Some("sandbox_id"))
2016 .await;
2017 let _: Result<(), _> = api.delete_all_builds("app_id", Some("sandbox_id")).await;
2018 let _: Result<(), _> = api
2019 .delete_sandbox_build("app_id", "build_id", "sandbox_id")
2020 .await;
2021 let _: Result<(), _> = api.delete_all_sandbox_builds("app_id", "sandbox_id").await;
2022
2023 Ok(())
2024 }
2025
2026 }
2029
2030 #[test]
2031 fn test_upload_large_file_request() {
2032 let request = UploadLargeFileRequest {
2033 app_id: "123".to_string(),
2034 file_path: "/path/to/large_file.jar".to_string(),
2035 filename: Some("custom_name.jar".to_string()),
2036 sandbox_id: Some("456".to_string()),
2037 };
2038
2039 assert_eq!(request.app_id, "123");
2040 assert_eq!(request.filename, Some("custom_name.jar".to_string()));
2041 assert_eq!(request.sandbox_id, Some("456".to_string()));
2042 }
2043
2044 #[test]
2045 fn test_upload_progress() {
2046 let progress = UploadProgress {
2047 bytes_uploaded: 1024,
2048 total_bytes: 2048,
2049 percentage: 50.0,
2050 };
2051
2052 assert_eq!(progress.bytes_uploaded, 1024);
2053 assert_eq!(progress.total_bytes, 2048);
2054 assert_eq!(progress.percentage, 50.0);
2055 }
2056
2057 #[test]
2058 fn test_large_file_scan_error_display() {
2059 let error = ScanError::FileTooLarge("File exceeds 2GB".to_string());
2060 assert_eq!(error.to_string(), "File too large: File exceeds 2GB");
2061
2062 let error = ScanError::UploadInProgress;
2063 assert_eq!(error.to_string(), "Upload or prescan already in progress");
2064
2065 let error = ScanError::ScanInProgress;
2066 assert_eq!(error.to_string(), "Scan in progress, cannot upload");
2067
2068 let error = ScanError::ChunkedUploadFailed("Network error".to_string());
2069 assert_eq!(error.to_string(), "Chunked upload failed: Network error");
2070 }
2071
2072 #[tokio::test]
2073 async fn test_large_file_upload_method_signatures() {
2074 async fn _test_large_file_methods() -> Result<(), Box<dyn std::error::Error>> {
2075 let config = VeracodeConfig::new("test", "test");
2076 let client = VeracodeClient::new(config)?;
2077 let api = client.scan_api()?;
2078
2079 let request = UploadLargeFileRequest {
2081 app_id: "123".to_string(),
2082 file_path: "/nonexistent/file.jar".to_string(),
2083 filename: None,
2084 sandbox_id: Some("456".to_string()),
2085 };
2086
2087 let _: Result<UploadedFile, _> = api.upload_large_file(request.clone()).await;
2090 let _: Result<UploadedFile, _> = api
2091 .upload_large_file_to_sandbox("123", "/path", "456", None)
2092 .await;
2093 let _: Result<UploadedFile, _> =
2094 api.upload_large_file_to_app("123", "/path", None).await;
2095
2096 let progress_callback = |bytes_uploaded: u64, total_bytes: u64, percentage: f64| {
2098 debug!("Upload progress: {bytes_uploaded}/{total_bytes} ({percentage:.1}%)");
2099 };
2100 let _: Result<UploadedFile, _> = api
2101 .upload_large_file_with_progress(request, progress_callback)
2102 .await;
2103
2104 Ok(())
2105 }
2106
2107 }
2110}