veracode_platform/
lib.rs

1//! # Veracode API Client Library
2//!
3//! A comprehensive Rust client library for interacting with Veracode APIs including
4//! Applications, Identity, Pipeline Scan, and Sandbox APIs.
5//!
6//! This library provides a safe and ergonomic interface to the Veracode platform,
7//! handling HMAC authentication, request/response serialization, and error handling.
8//!
9//! ## Features
10//!
11//! - ๐Ÿ” **HMAC Authentication** - Built-in support for Veracode API credentials
12//! - ๐ŸŒ **Multi-Regional Support** - Automatic endpoint routing for Commercial, European, and Federal regions
13//! - ๐Ÿ”„ **Smart API Routing** - Automatically uses REST or XML APIs based on the operation
14//! - ๐Ÿ“ฑ **Applications API** - Manage applications, builds, and scans (REST)
15//! - ๐Ÿ‘ค **Identity API** - User and team management (REST)
16//! - ๐Ÿ” **Pipeline Scan API** - Automated security scanning in CI/CD pipelines (REST)
17//! - ๐Ÿงช **Sandbox API** - Development sandbox management (REST)
18//! - ๐Ÿ“ค **Sandbox Scan API** - File upload and scan operations (XML)
19//! - ๐Ÿš€ **Async/Await** - Built on tokio for high-performance async operations
20//! - โšก **Type-Safe** - Full Rust type safety with serde serialization
21//! - ๐Ÿ“Š **Rich Data Types** - Comprehensive data structures for all API responses
22//!
23//! ## Quick Start
24//!
25//! ```no_run
26//! use veracode_platform::{VeracodeConfig, VeracodeClient, VeracodeRegion};
27//!
28//! #[tokio::main]
29//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
30//!     // Create configuration - automatically supports both API types
31//!     let config = VeracodeConfig::new(
32//!         "your_api_id".to_string(),
33//!         "your_api_key".to_string(),
34//!     ).with_region(VeracodeRegion::Commercial); // Optional: defaults to Commercial
35//!
36//!     let client = VeracodeClient::new(config)?;
37//!     
38//!     // REST API modules (use api.veracode.*)
39//!     let apps = client.get_all_applications().await?;
40//!     let pipeline = client.pipeline_api();
41//!     let identity = client.identity_api();
42//!     let sandbox = client.sandbox_api();  // REST API for sandbox management
43//!     let policy = client.policy_api();
44//!     
45//!     // XML API modules (automatically use analysiscenter.veracode.*)
46//!     let scan = client.scan_api(); // XML API for scanning
47//!     
48//!     Ok(())
49//! }
50//! ```
51//!
52//! ## Regional Support
53//!
54//! The library automatically handles regional endpoints for both API types:
55//!
56//! ```no_run
57//! use veracode_platform::{VeracodeConfig, VeracodeRegion};
58//!
59//! // European region
60//! let config = VeracodeConfig::new("api_id".to_string(), "api_key".to_string())
61//!     .with_region(VeracodeRegion::European);
62//! // REST APIs will use: api.veracode.eu
63//! // XML APIs will use: analysiscenter.veracode.eu
64//!
65//! // US Federal region  
66//! let config = VeracodeConfig::new("api_id".to_string(), "api_key".to_string())
67//!     .with_region(VeracodeRegion::Federal);
68//! // REST APIs will use: api.veracode.us
69//! // XML APIs will use: analysiscenter.veracode.us
70//! ```
71//!
72//! ## API Types
73//!
74//! Different Veracode modules use different API endpoints:
75//!
76//! - **REST API (api.veracode.*)**: Applications, Identity, Pipeline, Policy, Sandbox management
77//! - **XML API (analysiscenter.veracode.*)**: Sandbox scanning operations
78//!
79//! The client automatically routes each module to the correct API type based on the operation.
80//!
81//! ## Sandbox Operations
82//!
83//! Note that sandbox functionality is split across two modules:
84//!
85//! - **`sandbox_api()`** - Sandbox management (create, delete, list sandboxes) via REST API
86//! - **`scan_api()`** - File upload and scan operations via XML API
87//!
88//! This separation reflects the underlying Veracode API architecture where sandbox management
89//! uses the newer REST endpoints while scan operations use the legacy XML endpoints.
90
91pub mod app;
92pub mod build;
93pub mod client;
94pub mod identity;
95pub mod pipeline;
96pub mod policy;
97pub mod sandbox;
98pub mod scan;
99pub mod workflow;
100
101use reqwest::Error as ReqwestError;
102use std::fmt;
103
104// Re-export common types for convenience
105pub use app::{
106    Application, ApplicationQuery, ApplicationsResponse, CreateApplicationRequest,
107    UpdateApplicationRequest,
108};
109pub use build::{
110    Build, BuildApi, BuildError, BuildList, CreateBuildRequest, DeleteBuildRequest,
111    DeleteBuildResult, GetBuildInfoRequest, GetBuildListRequest, UpdateBuildRequest,
112};
113pub use client::VeracodeClient;
114pub use identity::{
115    ApiCredential, BusinessUnit, CreateApiCredentialRequest, CreateTeamRequest, CreateUserRequest,
116    IdentityApi, IdentityError, Role, Team, UpdateTeamRequest, UpdateUserRequest, User, UserQuery,
117    UserType,
118};
119pub use pipeline::{
120    CreateScanRequest, DevStage, Finding, FindingsSummary, PipelineApi, PipelineError, Scan,
121    ScanConfig, ScanResults, ScanStage, ScanStatus, SecurityStandards, Severity,
122};
123pub use policy::{
124    PolicyApi, PolicyComplianceResult, PolicyComplianceStatus, PolicyError, PolicyRule,
125    PolicyScanRequest, PolicyScanResult, PolicyThresholds, ScanType, SecurityPolicy,
126};
127pub use sandbox::{
128    ApiError, ApiErrorResponse, CreateSandboxRequest, Sandbox, SandboxApi, SandboxError,
129    SandboxListParams, SandboxScan, UpdateSandboxRequest,
130};
131pub use scan::{
132    BeginPreScanRequest, BeginScanRequest, PreScanMessage, PreScanResults, ScanApi, ScanError,
133    ScanInfo, ScanModule, UploadFileRequest, UploadLargeFileRequest, UploadProgress,
134    UploadProgressCallback, UploadedFile,
135};
136pub use workflow::{VeracodeWorkflow, WorkflowConfig, WorkflowError, WorkflowResultData};
137/// Custom error type for Veracode API operations.
138///
139/// This enum represents all possible errors that can occur when interacting
140/// with the Veracode Applications API.
141#[derive(Debug)]
142pub enum VeracodeError {
143    /// HTTP request failed
144    Http(ReqwestError),
145    /// JSON serialization/deserialization failed
146    Serialization(serde_json::Error),
147    /// Authentication error (invalid credentials, signature generation failure, etc.)
148    Authentication(String),
149    /// API returned an error response
150    InvalidResponse(String),
151    /// Configuration is invalid
152    InvalidConfig(String),
153    /// When an item is not found
154    NotFound(String),
155}
156
157impl VeracodeClient {
158    /// Create a specialized client for XML API operations.
159    ///
160    /// This internal method creates a client configured for the XML API
161    /// (analysiscenter.veracode.*) based on the current region settings.
162    /// Used exclusively for sandbox scan operations that require the XML API.
163    fn new_xml_client(config: VeracodeConfig) -> Result<Self, VeracodeError> {
164        let mut xml_config = config.clone();
165        xml_config.base_url = config.xml_base_url;
166        Self::new(xml_config)
167    }
168
169    /// Get an applications API instance.
170    /// Uses REST API (api.veracode.*).
171    pub fn applications_api(&self) -> &Self {
172        self
173    }
174
175    /// Get a sandbox API instance.
176    /// Uses REST API (api.veracode.*).
177    pub fn sandbox_api(&self) -> SandboxApi {
178        SandboxApi::new(self)
179    }
180
181    /// Get an identity API instance.
182    /// Uses REST API (api.veracode.*).
183    pub fn identity_api(&self) -> IdentityApi {
184        IdentityApi::new(self)
185    }
186
187    /// Get a pipeline scan API instance.
188    /// Uses REST API (api.veracode.*).
189    pub fn pipeline_api(&self) -> PipelineApi {
190        PipelineApi::new(self.clone())
191    }
192
193    /// Get a pipeline scan API instance with debug enabled.
194    /// Uses REST API (api.veracode.*).
195    pub fn pipeline_api_with_debug(&self, debug: bool) -> PipelineApi {
196        PipelineApi::new_with_debug(self.clone(), debug)
197    }
198
199    /// Get a policy API instance.
200    /// Uses REST API (api.veracode.*).
201    pub fn policy_api(&self) -> PolicyApi {
202        PolicyApi::new(self)
203    }
204
205    /// Get a scan API instance.
206    /// Uses XML API (analysiscenter.veracode.*) for both sandbox and application scans.
207    pub fn scan_api(&self) -> ScanApi {
208        // Create a specialized XML client for scan operations
209        let xml_client = Self::new_xml_client(self.config().clone()).unwrap();
210        ScanApi::new(xml_client)
211    }
212
213    /// Get a build API instance.
214    /// Uses XML API (analysiscenter.veracode.*) for build management operations.
215    pub fn build_api(&self) -> build::BuildApi {
216        // Create a specialized XML client for build operations
217        let xml_client = Self::new_xml_client(self.config().clone()).unwrap();
218        build::BuildApi::new(xml_client)
219    }
220
221    /// Get a workflow helper instance.
222    /// Provides high-level operations that combine multiple API calls.
223    pub fn workflow(&self) -> workflow::VeracodeWorkflow {
224        workflow::VeracodeWorkflow::new(self.clone())
225    }
226}
227
228impl fmt::Display for VeracodeError {
229    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
230        match self {
231            VeracodeError::Http(e) => write!(f, "HTTP error: {e}"),
232            VeracodeError::Serialization(e) => write!(f, "Serialization error: {e}"),
233            VeracodeError::Authentication(e) => write!(f, "Authentication error: {e}"),
234            VeracodeError::InvalidResponse(e) => write!(f, "Invalid response: {e}"),
235            VeracodeError::InvalidConfig(e) => write!(f, "Invalid configuration: {e}"),
236            VeracodeError::NotFound(e) => write!(f, "Item not found: {e}"),
237        }
238    }
239}
240
241impl std::error::Error for VeracodeError {}
242
243impl From<ReqwestError> for VeracodeError {
244    fn from(error: ReqwestError) -> Self {
245        VeracodeError::Http(error)
246    }
247}
248
249impl From<serde_json::Error> for VeracodeError {
250    fn from(error: serde_json::Error) -> Self {
251        VeracodeError::Serialization(error)
252    }
253}
254
255/// Secure wrapper for Veracode API ID that prevents exposure in debug output
256#[derive(Clone)]
257pub struct SecureVeracodeApiId(String);
258
259/// Secure wrapper for Veracode API key that prevents exposure in debug output
260#[derive(Clone)]
261pub struct SecureVeracodeApiKey(String);
262
263impl SecureVeracodeApiId {
264    pub fn new(api_id: String) -> Self {
265        SecureVeracodeApiId(api_id)
266    }
267
268    pub fn as_str(&self) -> &str {
269        &self.0
270    }
271
272    pub fn into_string(self) -> String {
273        self.0
274    }
275}
276
277impl SecureVeracodeApiKey {
278    pub fn new(api_key: String) -> Self {
279        SecureVeracodeApiKey(api_key)
280    }
281
282    pub fn as_str(&self) -> &str {
283        &self.0
284    }
285
286    pub fn into_string(self) -> String {
287        self.0
288    }
289}
290
291impl std::fmt::Debug for SecureVeracodeApiId {
292    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293        f.write_str("[REDACTED]")
294    }
295}
296
297impl std::fmt::Debug for SecureVeracodeApiKey {
298    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299        f.write_str("[REDACTED]")
300    }
301}
302
303/// Configuration for the Veracode API client.
304///
305/// This struct contains all the necessary configuration for connecting to
306/// the Veracode APIs, including authentication credentials and regional settings.
307/// It automatically manages both REST API (api.veracode.*) and XML API
308/// (analysiscenter.veracode.*) endpoints based on the selected region.
309#[derive(Clone)]
310pub struct VeracodeConfig {
311    /// Your Veracode API ID (securely wrapped)
312    pub api_id: SecureVeracodeApiId,
313    /// Your Veracode API key (securely wrapped, should be kept secret)
314    pub api_key: SecureVeracodeApiKey,
315    /// Base URL for the current client instance
316    pub base_url: String,
317    /// REST API base URL (api.veracode.*)
318    pub rest_base_url: String,
319    /// XML API base URL (analysiscenter.veracode.*)
320    pub xml_base_url: String,
321    /// Veracode region for your account
322    pub region: VeracodeRegion,
323    /// Whether to validate TLS certificates (default: true)
324    pub validate_certificates: bool,
325}
326
327/// Custom Debug implementation for VeracodeConfig that redacts sensitive information
328impl std::fmt::Debug for VeracodeConfig {
329    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330        f.debug_struct("VeracodeConfig")
331            .field("api_id", &self.api_id)
332            .field("api_key", &self.api_key)
333            .field("base_url", &self.base_url)
334            .field("rest_base_url", &self.rest_base_url)
335            .field("xml_base_url", &self.xml_base_url)
336            .field("region", &self.region)
337            .field("validate_certificates", &self.validate_certificates)
338            .finish()
339    }
340}
341
342/// Veracode regions for API access.
343///
344/// Different regions use different API endpoints. Choose the region
345/// that matches your Veracode account configuration.
346#[derive(Debug, Clone, Copy, PartialEq)]
347pub enum VeracodeRegion {
348    /// Commercial region (default) - api.veracode.com
349    Commercial,
350    /// European region - api.veracode.eu
351    European,
352    /// US Federal region - api.veracode.us
353    Federal,
354}
355
356impl VeracodeConfig {
357    /// Create a new configuration for the Commercial region.
358    ///
359    /// This creates a configuration that supports both REST API (api.veracode.*)
360    /// and XML API (analysiscenter.veracode.*) endpoints. The base_url defaults
361    /// to REST API for most modules, while sandbox scan operations automatically
362    /// use the XML API endpoint.
363    ///
364    /// # Arguments
365    ///
366    /// * `api_id` - Your Veracode API ID
367    /// * `api_key` - Your Veracode API key
368    ///
369    /// # Returns
370    ///
371    /// A new `VeracodeConfig` instance configured for the Commercial region.
372    pub fn new(api_id: String, api_key: String) -> Self {
373        Self {
374            api_id: SecureVeracodeApiId::new(api_id),
375            api_key: SecureVeracodeApiKey::new(api_key),
376            base_url: "https://api.veracode.com".to_string(),
377            rest_base_url: "https://api.veracode.com".to_string(),
378            xml_base_url: "https://analysiscenter.veracode.com".to_string(),
379            region: VeracodeRegion::Commercial,
380            validate_certificates: true, // Default to secure
381        }
382    }
383
384    /// Set the region for this configuration.
385    ///
386    /// This will automatically update both REST and XML API URLs to match the region.
387    /// All modules will use the appropriate regional endpoint for their API type.
388    ///
389    /// # Arguments
390    ///
391    /// * `region` - The Veracode region to use
392    ///
393    /// # Returns
394    ///
395    /// The updated configuration instance (for method chaining).
396    pub fn with_region(mut self, region: VeracodeRegion) -> Self {
397        let (rest_url, xml_url) = match region {
398            VeracodeRegion::Commercial => (
399                "https://api.veracode.com",
400                "https://analysiscenter.veracode.com",
401            ),
402            VeracodeRegion::European => (
403                "https://api.veracode.eu",
404                "https://analysiscenter.veracode.eu",
405            ),
406            VeracodeRegion::Federal => (
407                "https://api.veracode.us",
408                "https://analysiscenter.veracode.us",
409            ),
410        };
411
412        self.region = region;
413        self.rest_base_url = rest_url.to_string();
414        self.xml_base_url = xml_url.to_string();
415        self.base_url = self.rest_base_url.clone(); // Default to REST
416        self
417    }
418
419    /// Disable certificate validation for development environments.
420    ///
421    /// WARNING: This should only be used in development environments with
422    /// self-signed certificates. Never use this in production.
423    ///
424    /// # Returns
425    ///
426    /// The updated configuration instance (for method chaining).
427    pub fn with_certificate_validation_disabled(mut self) -> Self {
428        self.validate_certificates = false;
429        self
430    }
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436
437    #[test]
438    fn test_config_creation() {
439        let config = VeracodeConfig::new("test_api_id".to_string(), "test_api_key".to_string());
440
441        assert_eq!(config.api_id.as_str(), "test_api_id");
442        assert_eq!(config.api_key.as_str(), "test_api_key");
443        assert_eq!(config.base_url, "https://api.veracode.com");
444        assert_eq!(config.rest_base_url, "https://api.veracode.com");
445        assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
446        assert_eq!(config.region, VeracodeRegion::Commercial);
447        assert!(config.validate_certificates); // Default is secure
448    }
449
450    #[test]
451    fn test_european_region_config() {
452        let config = VeracodeConfig::new("test_api_id".to_string(), "test_api_key".to_string())
453            .with_region(VeracodeRegion::European);
454
455        assert_eq!(config.base_url, "https://api.veracode.eu");
456        assert_eq!(config.rest_base_url, "https://api.veracode.eu");
457        assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
458        assert_eq!(config.region, VeracodeRegion::European);
459    }
460
461    #[test]
462    fn test_federal_region_config() {
463        let config = VeracodeConfig::new("test_api_id".to_string(), "test_api_key".to_string())
464            .with_region(VeracodeRegion::Federal);
465
466        assert_eq!(config.base_url, "https://api.veracode.us");
467        assert_eq!(config.rest_base_url, "https://api.veracode.us");
468        assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
469        assert_eq!(config.region, VeracodeRegion::Federal);
470    }
471
472    #[test]
473    fn test_certificate_validation_disabled() {
474        let config = VeracodeConfig::new("test_api_id".to_string(), "test_api_key".to_string())
475            .with_certificate_validation_disabled();
476
477        assert!(!config.validate_certificates);
478    }
479
480    #[test]
481    fn test_secure_api_id_debug_redaction() {
482        let api_id = SecureVeracodeApiId::new("test_api_id_123".to_string());
483        let debug_output = format!("{api_id:?}");
484        assert_eq!(debug_output, "[REDACTED]");
485        assert!(!debug_output.contains("test_api_id_123"));
486    }
487
488    #[test]
489    fn test_secure_api_key_debug_redaction() {
490        let api_key = SecureVeracodeApiKey::new("test_api_key_456".to_string());
491        let debug_output = format!("{api_key:?}");
492        assert_eq!(debug_output, "[REDACTED]");
493        assert!(!debug_output.contains("test_api_key_456"));
494    }
495
496    #[test]
497    fn test_veracode_config_debug_redaction() {
498        let config = VeracodeConfig::new(
499            "test_api_id_123".to_string(),
500            "test_api_key_456".to_string(),
501        );
502        let debug_output = format!("{config:?}");
503
504        // Should show structure but redact actual values
505        assert!(debug_output.contains("VeracodeConfig"));
506        assert!(debug_output.contains("api_id"));
507        assert!(debug_output.contains("api_key"));
508        assert!(debug_output.contains("[REDACTED]"));
509
510        // Should not contain actual credential values
511        assert!(!debug_output.contains("test_api_id_123"));
512        assert!(!debug_output.contains("test_api_key_456"));
513    }
514
515    #[test]
516    fn test_secure_api_id_access_methods() {
517        let api_id = SecureVeracodeApiId::new("test_api_id_123".to_string());
518
519        // Test as_str method
520        assert_eq!(api_id.as_str(), "test_api_id_123");
521
522        // Test into_string method
523        let string_value = api_id.into_string();
524        assert_eq!(string_value, "test_api_id_123");
525    }
526
527    #[test]
528    fn test_secure_api_key_access_methods() {
529        let api_key = SecureVeracodeApiKey::new("test_api_key_456".to_string());
530
531        // Test as_str method
532        assert_eq!(api_key.as_str(), "test_api_key_456");
533
534        // Test into_string method
535        let string_value = api_key.into_string();
536        assert_eq!(string_value, "test_api_key_456");
537    }
538
539    #[test]
540    fn test_secure_api_credentials_clone() {
541        let api_id = SecureVeracodeApiId::new("test_api_id_123".to_string());
542        let api_key = SecureVeracodeApiKey::new("test_api_key_456".to_string());
543
544        let cloned_api_id = api_id.clone();
545        let cloned_api_key = api_key.clone();
546
547        // Both should have the same values
548        assert_eq!(api_id.as_str(), cloned_api_id.as_str());
549        assert_eq!(api_key.as_str(), cloned_api_key.as_str());
550    }
551
552    #[test]
553    fn test_error_display() {
554        let error = VeracodeError::Authentication("Invalid API key".to_string());
555        assert_eq!(format!("{error}"), "Authentication error: Invalid API key");
556    }
557
558    #[test]
559    fn test_error_from_reqwest() {
560        // Test that we can convert from reqwest errors
561        // Note: We can't easily create a reqwest::Error for testing,
562        // so we'll just verify the From trait implementation exists
563        // by checking that it compiles
564        fn _test_conversion(error: reqwest::Error) -> VeracodeError {
565            VeracodeError::from(error)
566        }
567
568        // If this compiles, the From trait is implemented correctly
569        // Test passes if no panic occurs
570    }
571}