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}