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 client;
92pub mod app;
93pub mod sandbox;
94pub mod scan;
95pub mod build;
96pub mod identity;
97pub mod pipeline;
98pub mod policy;
99pub mod workflow;
100
101use std::fmt;
102use reqwest::Error as ReqwestError;
103
104// Re-export common types for convenience
105pub use client::VeracodeClient;
106pub use app::{Application, ApplicationQuery, ApplicationsResponse, CreateApplicationRequest, UpdateApplicationRequest};
107pub use sandbox::{
108    SandboxApi, Sandbox, CreateSandboxRequest, UpdateSandboxRequest, 
109    SandboxListParams, SandboxError, SandboxScan, ApiErrorResponse, ApiError
110};
111pub use scan::{
112    ScanApi, UploadedFile, PreScanResults, ScanInfo, ScanModule, PreScanMessage,
113    UploadFileRequest, UploadLargeFileRequest, BeginPreScanRequest, BeginScanRequest, 
114    ScanError, UploadProgress, UploadProgressCallback
115};
116pub use build::{
117    BuildApi, Build, BuildList, CreateBuildRequest, UpdateBuildRequest, DeleteBuildRequest,
118    GetBuildInfoRequest, GetBuildListRequest, DeleteBuildResult, BuildError
119};
120pub use identity::{
121    IdentityApi, User, UserType, Role, Team, BusinessUnit, ApiCredential,
122    CreateUserRequest, UpdateUserRequest, CreateTeamRequest, UpdateTeamRequest,
123    CreateApiCredentialRequest, UserQuery, IdentityError
124};
125pub use pipeline::{
126    PipelineApi, Finding, Scan, ScanResults, CreateScanRequest, ScanConfig,
127    DevStage, ScanStage, ScanStatus, Severity, FindingsSummary, SecurityStandards, PipelineError
128};
129pub use policy::{
130    PolicyApi, SecurityPolicy, PolicyScanRequest, PolicyScanResult, PolicyComplianceResult,
131    PolicyComplianceStatus, ScanType, PolicyRule, PolicyThresholds, PolicyError
132};
133pub use workflow::{
134    VeracodeWorkflow, WorkflowConfig, WorkflowError, WorkflowResultData
135};
136/// Custom error type for Veracode API operations.
137///
138/// This enum represents all possible errors that can occur when interacting
139/// with the Veracode Applications API.
140#[derive(Debug)]
141pub enum VeracodeError {
142    /// HTTP request failed
143    Http(ReqwestError),
144    /// JSON serialization/deserialization failed
145    Serialization(serde_json::Error),
146    /// Authentication error (invalid credentials, signature generation failure, etc.)
147    Authentication(String),
148    /// API returned an error response
149    InvalidResponse(String),
150    /// Configuration is invalid
151    InvalidConfig(String),
152    /// When an item is not found
153    NotFound(String),
154}
155
156impl VeracodeClient {
157    /// Create a specialized client for XML API operations.
158    ///
159    /// This internal method creates a client configured for the XML API
160    /// (analysiscenter.veracode.*) based on the current region settings.
161    /// Used exclusively for sandbox scan operations that require the XML API.
162    fn new_xml_client(config: VeracodeConfig) -> Result<Self, VeracodeError> {
163        let mut xml_config = config.clone();
164        xml_config.base_url = config.xml_base_url;
165        Self::new(xml_config)
166    }
167
168    /// Get an applications API instance.
169    /// Uses REST API (api.veracode.*).
170    pub fn applications_api(&self) -> &Self {
171        self
172    }
173
174    /// Get a sandbox API instance.
175    /// Uses REST API (api.veracode.*).
176    pub fn sandbox_api(&self) -> SandboxApi {
177        SandboxApi::new(self)
178    }
179
180    /// Get an identity API instance.
181    /// Uses REST API (api.veracode.*).
182    pub fn identity_api(&self) -> IdentityApi {
183        IdentityApi::new(self)
184    }
185
186    /// Get a pipeline scan API instance.
187    /// Uses REST API (api.veracode.*).
188    pub fn pipeline_api(&self) -> PipelineApi {
189        PipelineApi::new(self.clone())
190    }
191
192    /// Get a pipeline scan API instance with debug enabled.
193    /// Uses REST API (api.veracode.*).
194    pub fn pipeline_api_with_debug(&self, debug: bool) -> PipelineApi {
195        PipelineApi::new_with_debug(self.clone(), debug)
196    }
197
198    /// Get a policy API instance.
199    /// Uses REST API (api.veracode.*).
200    pub fn policy_api(&self) -> PolicyApi {
201        PolicyApi::new(self)
202    }
203
204    /// Get a scan API instance.
205    /// Uses XML API (analysiscenter.veracode.*) for both sandbox and application scans.
206    pub fn scan_api(&self) -> ScanApi {
207        // Create a specialized XML client for scan operations
208        let xml_client = Self::new_xml_client(self.config().clone()).unwrap();
209        ScanApi::new(xml_client)
210    }
211
212    /// Get a build API instance.
213    /// Uses XML API (analysiscenter.veracode.*) for build management operations.
214    pub fn build_api(&self) -> build::BuildApi {
215        // Create a specialized XML client for build operations
216        let xml_client = Self::new_xml_client(self.config().clone()).unwrap();
217        build::BuildApi::new(xml_client)
218    }
219
220    /// Get a workflow helper instance.
221    /// Provides high-level operations that combine multiple API calls.
222    pub fn workflow(&self) -> workflow::VeracodeWorkflow {
223        workflow::VeracodeWorkflow::new(self.clone())
224    }
225}
226
227impl fmt::Display for VeracodeError {
228    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229        match self {
230            VeracodeError::Http(e) => write!(f, "HTTP error: {e}"),
231            VeracodeError::Serialization(e) => write!(f, "Serialization error: {e}"),
232            VeracodeError::Authentication(e) => write!(f, "Authentication error: {e}"),
233            VeracodeError::InvalidResponse(e) => write!(f, "Invalid response: {e}"),
234            VeracodeError::InvalidConfig(e) => write!(f, "Invalid configuration: {e}"),
235            VeracodeError::NotFound(e) => write!(f, "Item not found: {e}"),
236        }
237    }
238}
239
240impl std::error::Error for VeracodeError {}
241
242impl From<ReqwestError> for VeracodeError {
243    fn from(error: ReqwestError) -> Self {
244        VeracodeError::Http(error)
245    }
246}
247
248impl From<serde_json::Error> for VeracodeError {
249    fn from(error: serde_json::Error) -> Self {
250        VeracodeError::Serialization(error)
251    }
252}
253
254/// Configuration for the Veracode API client.
255///
256/// This struct contains all the necessary configuration for connecting to
257/// the Veracode APIs, including authentication credentials and regional settings.
258/// It automatically manages both REST API (api.veracode.*) and XML API 
259/// (analysiscenter.veracode.*) endpoints based on the selected region.
260#[derive(Debug, Clone)]
261pub struct VeracodeConfig {
262    /// Your Veracode API ID
263    pub api_id: String,
264    /// Your Veracode API key (should be kept secret)
265    pub api_key: String,
266    /// Base URL for the current client instance
267    pub base_url: String,
268    /// REST API base URL (api.veracode.*)
269    pub rest_base_url: String,
270    /// XML API base URL (analysiscenter.veracode.*)
271    pub xml_base_url: String,
272    /// Veracode region for your account
273    pub region: VeracodeRegion,
274    /// Whether to validate TLS certificates (default: true)
275    pub validate_certificates: bool,
276}
277
278/// Veracode regions for API access.
279///
280/// Different regions use different API endpoints. Choose the region
281/// that matches your Veracode account configuration.
282#[derive(Debug, Clone, Copy, PartialEq)]
283pub enum VeracodeRegion {
284    /// Commercial region (default) - api.veracode.com
285    Commercial,
286    /// European region - api.veracode.eu
287    European,
288    /// US Federal region - api.veracode.us
289    Federal,
290}
291
292impl VeracodeConfig {
293    /// Create a new configuration for the Commercial region.
294    ///
295    /// This creates a configuration that supports both REST API (api.veracode.*)
296    /// and XML API (analysiscenter.veracode.*) endpoints. The base_url defaults
297    /// to REST API for most modules, while sandbox scan operations automatically
298    /// use the XML API endpoint.
299    ///
300    /// # Arguments
301    ///
302    /// * `api_id` - Your Veracode API ID
303    /// * `api_key` - Your Veracode API key
304    ///
305    /// # Returns
306    ///
307    /// A new `VeracodeConfig` instance configured for the Commercial region.
308    pub fn new(api_id: String, api_key: String) -> Self {
309        Self {
310            api_id,
311            api_key,
312            base_url: "https://api.veracode.com".to_string(),
313            rest_base_url: "https://api.veracode.com".to_string(),
314            xml_base_url: "https://analysiscenter.veracode.com".to_string(),
315            region: VeracodeRegion::Commercial,
316            validate_certificates: true, // Default to secure
317        }
318    }
319
320    /// Set the region for this configuration.
321    ///
322    /// This will automatically update both REST and XML API URLs to match the region.
323    /// All modules will use the appropriate regional endpoint for their API type.
324    ///
325    /// # Arguments
326    ///
327    /// * `region` - The Veracode region to use
328    ///
329    /// # Returns
330    ///
331    /// The updated configuration instance (for method chaining).
332    pub fn with_region(mut self, region: VeracodeRegion) -> Self {
333        let (rest_url, xml_url) = match region {
334            VeracodeRegion::Commercial => (
335                "https://api.veracode.com",
336                "https://analysiscenter.veracode.com"
337            ),
338            VeracodeRegion::European => (
339                "https://api.veracode.eu",
340                "https://analysiscenter.veracode.eu"
341            ),
342            VeracodeRegion::Federal => (
343                "https://api.veracode.us",
344                "https://analysiscenter.veracode.us"
345            ),
346        };
347        
348        self.region = region;
349        self.rest_base_url = rest_url.to_string();
350        self.xml_base_url = xml_url.to_string();
351        self.base_url = self.rest_base_url.clone(); // Default to REST
352        self
353    }
354
355    /// Disable certificate validation for development environments.
356    ///
357    /// WARNING: This should only be used in development environments with
358    /// self-signed certificates. Never use this in production.
359    ///
360    /// # Returns
361    ///
362    /// The updated configuration instance (for method chaining).
363    pub fn with_certificate_validation_disabled(mut self) -> Self {
364        self.validate_certificates = false;
365        self
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372
373    #[test]
374    fn test_config_creation() {
375        let config = VeracodeConfig::new(
376            "test_api_id".to_string(),
377            "test_api_key".to_string(),
378        );
379        
380        assert_eq!(config.api_id, "test_api_id");
381        assert_eq!(config.api_key, "test_api_key");
382        assert_eq!(config.base_url, "https://api.veracode.com");
383        assert_eq!(config.rest_base_url, "https://api.veracode.com");
384        assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.com");
385        assert_eq!(config.region, VeracodeRegion::Commercial);
386        assert!(config.validate_certificates); // Default is secure
387    }
388
389    #[test]
390    fn test_european_region_config() {
391        let config = VeracodeConfig::new(
392            "test_api_id".to_string(),
393            "test_api_key".to_string(),
394        ).with_region(VeracodeRegion::European);
395        
396        assert_eq!(config.base_url, "https://api.veracode.eu");
397        assert_eq!(config.rest_base_url, "https://api.veracode.eu");
398        assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.eu");
399        assert_eq!(config.region, VeracodeRegion::European);
400    }
401
402    #[test]
403    fn test_federal_region_config() {
404        let config = VeracodeConfig::new(
405            "test_api_id".to_string(),
406            "test_api_key".to_string(),
407        ).with_region(VeracodeRegion::Federal);
408        
409        assert_eq!(config.base_url, "https://api.veracode.us");
410        assert_eq!(config.rest_base_url, "https://api.veracode.us");
411        assert_eq!(config.xml_base_url, "https://analysiscenter.veracode.us");
412        assert_eq!(config.region, VeracodeRegion::Federal);
413    }
414
415    #[test]
416    fn test_certificate_validation_disabled() {
417        let config = VeracodeConfig::new(
418            "test_api_id".to_string(),
419            "test_api_key".to_string(),
420        ).with_certificate_validation_disabled();
421        
422        assert!(!config.validate_certificates);
423    }
424
425    #[test]
426    fn test_error_display() {
427        let error = VeracodeError::Authentication("Invalid API key".to_string());
428        assert_eq!(format!("{error}"), "Authentication error: Invalid API key");
429    }
430
431    #[test]
432    fn test_error_from_reqwest() {
433        // Test that we can convert from reqwest errors
434        // Note: We can't easily create a reqwest::Error for testing,
435        // so we'll just verify the From trait implementation exists
436        // by checking that it compiles
437        fn _test_conversion(error: reqwest::Error) -> VeracodeError {
438            VeracodeError::from(error)
439        }
440        
441        // If this compiles, the From trait is implemented correctly
442        assert!(true);
443    }
444}