veracode_platform/
policy.rs

1//! Policy API module for Veracode Platform
2//!
3//! This module provides functionality for managing security policies, policy compliance,
4//! and policy scan operations within the Veracode platform.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10use crate::{VeracodeClient, VeracodeError};
11
12/// Represents a security policy in the Veracode platform
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct SecurityPolicy {
15    /// Globally unique identifier for the policy
16    pub guid: String,
17    /// Policy name
18    pub name: String,
19    /// Policy description
20    pub description: Option<String>,
21    /// Policy type (CUSTOMER, BUILTIN, STANDARD)
22    #[serde(rename = "type")]
23    pub policy_type: String,
24    /// Policy version number
25    pub version: u32,
26    /// When the policy was created
27    pub created: Option<DateTime<Utc>>,
28    /// Who modified the policy last
29    pub modified_by: Option<String>,
30    /// Organization ID this policy belongs to
31    pub organization_id: Option<u64>,
32    /// Policy category (APPLICATION, etc.)
33    pub category: String,
34    /// Whether this is a vendor policy
35    pub vendor_policy: bool,
36    /// Scan frequency rules
37    pub scan_frequency_rules: Vec<ScanFrequencyRule>,
38    /// Finding rules for the policy
39    pub finding_rules: Vec<FindingRule>,
40    /// Custom severities defined for this policy
41    pub custom_severities: Vec<serde_json::Value>,
42    /// Grace periods for different severity levels
43    pub sev5_grace_period: u32,
44    pub sev4_grace_period: u32,
45    pub sev3_grace_period: u32,
46    pub sev2_grace_period: u32,
47    pub sev1_grace_period: u32,
48    pub sev0_grace_period: u32,
49    /// Score grace period
50    pub score_grace_period: u32,
51    /// SCA blacklist grace period
52    pub sca_blacklist_grace_period: u32,
53    /// SCA grace periods (nullable)
54    pub sca_grace_periods: Option<serde_json::Value>,
55    /// Evaluation date
56    pub evaluation_date: Option<DateTime<Utc>>,
57    /// Evaluation date type
58    pub evaluation_date_type: Option<String>,
59    /// Policy capabilities
60    pub capabilities: Vec<String>,
61    /// Links for API navigation
62    #[serde(rename = "_links")]
63    pub links: Option<serde_json::Value>,
64}
65
66/// Policy compliance status
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "UPPERCASE")]
69pub enum PolicyComplianceStatus {
70    /// Application passes all policy requirements
71    Pass,
72    /// Application fails policy requirements
73    Fail,
74    /// Policy compliance check is pending
75    Pending,
76    /// Policy compliance status is not determined
77    NotDetermined,
78    /// Policy compliance check resulted in error
79    Error,
80}
81
82/// Individual policy rule
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct PolicyRule {
85    /// Rule identifier
86    pub id: String,
87    /// Rule name
88    pub name: String,
89    /// Rule description
90    pub description: Option<String>,
91    /// Rule type (e.g., severity, category)
92    pub rule_type: String,
93    /// Rule criteria
94    pub criteria: Option<serde_json::Value>,
95    /// Whether the rule is enabled
96    pub enabled: bool,
97    /// Rule severity level
98    pub severity: Option<String>,
99}
100
101/// Policy compliance thresholds
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct PolicyThresholds {
104    /// Maximum allowed Very High severity flaws
105    pub very_high: Option<u32>,
106    /// Maximum allowed High severity flaws
107    pub high: Option<u32>,
108    /// Maximum allowed Medium severity flaws
109    pub medium: Option<u32>,
110    /// Maximum allowed Low severity flaws
111    pub low: Option<u32>,
112    /// Maximum allowed Very Low severity flaws
113    pub very_low: Option<u32>,
114    /// Overall score threshold
115    pub score_threshold: Option<f64>,
116}
117
118/// Policy scan request
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct PolicyScanRequest {
121    /// Application GUID to scan
122    pub application_guid: String,
123    /// Policy GUID to apply
124    pub policy_guid: String,
125    /// Scan type (static, dynamic, sca)
126    pub scan_type: ScanType,
127    /// Optional sandbox GUID for sandbox scans
128    pub sandbox_guid: Option<String>,
129    /// Scan configuration
130    pub config: Option<PolicyScanConfig>,
131}
132
133/// Types of scans for policy evaluation
134#[derive(Debug, Clone, Serialize, Deserialize)]
135#[serde(rename_all = "lowercase")]
136pub enum ScanType {
137    /// Static Application Security Testing
138    Static,
139    /// Dynamic Application Security Testing
140    Dynamic,
141    /// Software Composition Analysis
142    Sca,
143    /// Manual penetration testing
144    Manual,
145}
146
147/// Configuration for policy scans
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct PolicyScanConfig {
150    /// Whether to auto-submit the scan
151    pub auto_submit: Option<bool>,
152    /// Scan timeout in minutes
153    pub timeout_minutes: Option<u32>,
154    /// Include third-party components
155    pub include_third_party: Option<bool>,
156    /// Scan modules to include
157    pub modules: Option<Vec<String>>,
158}
159
160/// Policy scan result
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct PolicyScanResult {
163    /// Scan identifier
164    pub scan_id: u64,
165    /// Application GUID
166    pub application_guid: String,
167    /// Policy GUID used for evaluation
168    pub policy_guid: String,
169    /// Scan status
170    pub status: ScanStatus,
171    /// Scan type
172    pub scan_type: ScanType,
173    /// When the scan was initiated
174    pub started: DateTime<Utc>,
175    /// When the scan completed
176    pub completed: Option<DateTime<Utc>>,
177    /// Policy compliance result
178    pub compliance_result: Option<PolicyComplianceResult>,
179    /// Findings summary
180    pub findings_summary: Option<FindingsSummary>,
181    /// URL to detailed results
182    pub results_url: Option<String>,
183}
184
185/// Status of a policy scan
186#[derive(Debug, Clone, Serialize, Deserialize)]
187#[serde(rename_all = "UPPERCASE")]
188pub enum ScanStatus {
189    /// Scan is queued for processing
190    Queued,
191    /// Scan is currently running
192    Running,
193    /// Scan completed successfully
194    Completed,
195    /// Scan failed
196    Failed,
197    /// Scan was cancelled
198    Cancelled,
199    /// Scan timed out
200    Timeout,
201}
202
203/// Policy compliance evaluation result
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct PolicyComplianceResult {
206    /// Overall compliance status
207    pub status: PolicyComplianceStatus,
208    /// Compliance score (0-100)
209    pub score: Option<f64>,
210    /// Whether scan passed policy requirements
211    pub passed: bool,
212    /// Detailed compliance breakdown
213    pub breakdown: Option<ComplianceBreakdown>,
214    /// Policy violations found
215    pub violations: Option<Vec<PolicyViolation>>,
216    /// Compliance summary message
217    pub summary: Option<String>,
218}
219
220/// Detailed compliance breakdown by severity
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct ComplianceBreakdown {
223    /// Very High severity findings count
224    pub very_high: u32,
225    /// High severity findings count
226    pub high: u32,
227    /// Medium severity findings count
228    pub medium: u32,
229    /// Low severity findings count
230    pub low: u32,
231    /// Very Low severity findings count
232    pub very_low: u32,
233    /// Total findings count
234    pub total: u32,
235}
236
237/// Policy violation details
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct PolicyViolation {
240    /// Violation type
241    pub violation_type: String,
242    /// Severity of the violation
243    pub severity: String,
244    /// Description of the violation
245    pub description: String,
246    /// Count of this violation type
247    pub count: u32,
248    /// Threshold that was exceeded
249    pub threshold_exceeded: Option<u32>,
250    /// Actual value that caused the violation
251    pub actual_value: Option<u32>,
252}
253
254/// Summary of findings from a policy scan
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct FindingsSummary {
257    /// Total number of findings
258    pub total: u32,
259    /// Number of open findings
260    pub open: u32,
261    /// Number of fixed findings
262    pub fixed: u32,
263    /// Number of findings by severity
264    pub by_severity: HashMap<String, u32>,
265    /// Number of findings by category
266    pub by_category: Option<HashMap<String, u32>>,
267}
268
269/// Scan frequency rule for policies
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct ScanFrequencyRule {
272    /// Type of scan this rule applies to
273    pub scan_type: String,
274    /// How frequently scans should be performed
275    pub frequency: String,
276}
277
278/// Finding rule for policies
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct FindingRule {
281    /// Type of finding rule
282    #[serde(rename = "type")]
283    pub rule_type: String,
284    /// Scan types this rule applies to
285    pub scan_type: Vec<String>,
286    /// Rule value/threshold
287    pub value: String,
288    /// Advanced options for the rule
289    pub advanced_options: Option<serde_json::Value>,
290}
291
292/// Advanced options for finding rules
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct FindingRuleAdvancedOptions {
295    /// Override severity
296    pub override_severity: Option<bool>,
297    /// Build action (WARNING, ERROR, etc.)
298    pub build_action: Option<String>,
299    /// Component dependency type
300    pub component_dependency: Option<String>,
301    /// Vulnerable methods setting
302    pub vulnerable_methods: Option<String>,
303    /// Selected licenses
304    pub selected_licenses: Option<Vec<String>>,
305    /// Override severity level
306    pub override_severity_level: Option<String>,
307    /// Whether to allow non-OSS licenses
308    pub allowed_nonoss_licenses: Option<bool>,
309    /// Whether to allow unrecognized licenses
310    pub allowed_unrecognized_licenses: Option<bool>,
311    /// Whether all licenses must meet requirement
312    pub all_licenses_must_meet_requirement: Option<bool>,
313    /// Whether this is a blocklist
314    pub is_blocklist: Option<bool>,
315}
316
317/// Query parameters for listing policies
318#[derive(Debug, Clone, Default)]
319pub struct PolicyListParams {
320    /// Filter by policy name
321    pub name: Option<String>,
322    /// Filter by policy type
323    pub policy_type: Option<String>,
324    /// Filter by active status
325    pub is_active: Option<bool>,
326    /// Include only default policies
327    pub default_only: Option<bool>,
328    /// Page number for pagination
329    pub page: Option<u32>,
330    /// Number of items per page
331    pub size: Option<u32>,
332}
333
334impl PolicyListParams {
335    /// Convert to query parameters for HTTP requests
336    pub fn to_query_params(&self) -> Vec<(String, String)> {
337        let mut params = Vec::new();
338
339        if let Some(name) = &self.name {
340            params.push(("name".to_string(), name.clone()));
341        }
342        if let Some(policy_type) = &self.policy_type {
343            params.push(("type".to_string(), policy_type.clone()));
344        }
345        if let Some(is_active) = self.is_active {
346            params.push(("active".to_string(), is_active.to_string()));
347        }
348        if let Some(default_only) = self.default_only {
349            params.push(("default".to_string(), default_only.to_string()));
350        }
351        if let Some(page) = self.page {
352            params.push(("page".to_string(), page.to_string()));
353        }
354        if let Some(size) = self.size {
355            params.push(("size".to_string(), size.to_string()));
356        }
357
358        params
359    }
360}
361
362/// Response wrapper for policy list operations
363#[derive(Debug, Clone, Serialize, Deserialize)]
364pub struct PolicyListResponse {
365    #[serde(rename = "_embedded")]
366    pub embedded: Option<PolicyEmbedded>,
367    pub page: Option<PageInfo>,
368    #[serde(rename = "_links")]
369    pub links: Option<serde_json::Value>,
370}
371
372/// Embedded policies in the list response
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct PolicyEmbedded {
375    #[serde(rename = "policy_versions")]
376    pub policy_versions: Vec<SecurityPolicy>,
377}
378
379/// Page information for paginated responses
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct PageInfo {
382    pub size: u32,
383    pub number: u32,
384    pub total_elements: u32,
385    pub total_pages: u32,
386}
387
388/// Policy-specific error types
389#[derive(Debug)]
390pub enum PolicyError {
391    /// Veracode API error
392    Api(VeracodeError),
393    /// Policy not found (404)
394    NotFound,
395    /// Invalid policy configuration (400)
396    InvalidConfig(String),
397    /// Policy scan failed
398    ScanFailed(String),
399    /// Policy evaluation error
400    EvaluationError(String),
401    /// Insufficient permissions (403)
402    PermissionDenied,
403    /// Authentication required (401)
404    Unauthorized,
405    /// Internal server error (500)
406    InternalServerError,
407    /// Policy compliance check timeout
408    Timeout,
409}
410
411impl std::fmt::Display for PolicyError {
412    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413        match self {
414            PolicyError::Api(err) => write!(f, "API error: {err}"),
415            PolicyError::NotFound => write!(f, "Policy not found"),
416            PolicyError::InvalidConfig(msg) => write!(f, "Invalid policy configuration: {msg}"),
417            PolicyError::ScanFailed(msg) => write!(f, "Policy scan failed: {msg}"),
418            PolicyError::EvaluationError(msg) => write!(f, "Policy evaluation error: {msg}"),
419            PolicyError::PermissionDenied => {
420                write!(f, "Insufficient permissions for policy operation")
421            }
422            PolicyError::Unauthorized => {
423                write!(f, "Authentication required - invalid API credentials")
424            }
425            PolicyError::InternalServerError => write!(f, "Internal server error occurred"),
426            PolicyError::Timeout => write!(f, "Policy operation timed out"),
427        }
428    }
429}
430
431impl std::error::Error for PolicyError {}
432
433impl From<VeracodeError> for PolicyError {
434    fn from(err: VeracodeError) -> Self {
435        PolicyError::Api(err)
436    }
437}
438
439impl From<reqwest::Error> for PolicyError {
440    fn from(err: reqwest::Error) -> Self {
441        PolicyError::Api(VeracodeError::Http(err))
442    }
443}
444
445impl From<serde_json::Error> for PolicyError {
446    fn from(err: serde_json::Error) -> Self {
447        PolicyError::Api(VeracodeError::Serialization(err))
448    }
449}
450
451/// Veracode Policy API operations
452pub struct PolicyApi<'a> {
453    client: &'a VeracodeClient,
454}
455
456impl<'a> PolicyApi<'a> {
457    /// Create a new PolicyApi instance
458    pub fn new(client: &'a VeracodeClient) -> Self {
459        Self { client }
460    }
461
462    /// List all available security policies
463    ///
464    /// # Arguments
465    ///
466    /// * `params` - Optional query parameters for filtering
467    ///
468    /// # Returns
469    ///
470    /// A `Result` containing a list of policies or an error.
471    pub async fn list_policies(
472        &self,
473        params: Option<PolicyListParams>,
474    ) -> Result<Vec<SecurityPolicy>, PolicyError> {
475        let endpoint = "/appsec/v1/policies";
476
477        let query_params = params.map(|p| p.to_query_params());
478
479        let response = self.client.get(endpoint, query_params.as_deref()).await?;
480
481        let status = response.status().as_u16();
482        match status {
483            200 => {
484                let policy_response: PolicyListResponse = response.json().await?;
485                let policies = policy_response
486                    .embedded
487                    .map(|e| e.policy_versions)
488                    .unwrap_or_default();
489
490                Ok(policies)
491            }
492            400 => {
493                let error_text = response.text().await.unwrap_or_default();
494                Err(PolicyError::InvalidConfig(error_text))
495            }
496            401 => Err(PolicyError::Unauthorized),
497            403 => Err(PolicyError::PermissionDenied),
498            404 => Err(PolicyError::NotFound),
499            500 => Err(PolicyError::InternalServerError),
500            _ => {
501                let error_text = response.text().await.unwrap_or_default();
502                Err(PolicyError::Api(VeracodeError::InvalidResponse(format!(
503                    "HTTP {status}: {error_text}"
504                ))))
505            }
506        }
507    }
508
509    /// Get a specific policy by GUID
510    ///
511    /// # Arguments
512    ///
513    /// * `policy_guid` - The GUID of the policy
514    ///
515    /// # Returns
516    ///
517    /// A `Result` containing the policy or an error.
518    pub async fn get_policy(&self, policy_guid: &str) -> Result<SecurityPolicy, PolicyError> {
519        let endpoint = format!("/appsec/v1/policies/{policy_guid}");
520
521        let response = self.client.get(&endpoint, None).await?;
522
523        let status = response.status().as_u16();
524        match status {
525            200 => {
526                let policy: SecurityPolicy = response.json().await?;
527                Ok(policy)
528            }
529            400 => {
530                let error_text = response.text().await.unwrap_or_default();
531                Err(PolicyError::InvalidConfig(error_text))
532            }
533            401 => Err(PolicyError::Unauthorized),
534            403 => Err(PolicyError::PermissionDenied),
535            404 => Err(PolicyError::NotFound),
536            500 => Err(PolicyError::InternalServerError),
537            _ => {
538                let error_text = response.text().await.unwrap_or_default();
539                Err(PolicyError::Api(VeracodeError::InvalidResponse(format!(
540                    "HTTP {status}: {error_text}"
541                ))))
542            }
543        }
544    }
545
546    /// Get the default policy for the organization
547    ///
548    /// # Returns
549    ///
550    /// A `Result` containing the default policy or an error.
551    pub async fn get_default_policy(&self) -> Result<SecurityPolicy, PolicyError> {
552        let params = PolicyListParams {
553            default_only: Some(true),
554            ..Default::default()
555        };
556
557        let policies = self.list_policies(Some(params)).await?;
558        // Note: Default policy identification may need to be handled differently
559        // based on the actual API response structure
560        policies
561            .into_iter()
562            .find(|p| p.policy_type == "CUSTOMER" && p.organization_id.is_some())
563            .ok_or(PolicyError::NotFound)
564    }
565
566    /// Evaluate policy compliance for an application
567    ///
568    /// # Arguments
569    ///
570    /// * `application_guid` - The GUID of the application
571    /// * `policy_guid` - The GUID of the policy to evaluate against
572    /// * `sandbox_guid` - Optional sandbox GUID for sandbox evaluation
573    ///
574    /// # Returns
575    ///
576    /// A `Result` containing the compliance result or an error.
577    pub async fn evaluate_policy_compliance(
578        &self,
579        application_guid: &str,
580        policy_guid: &str,
581        sandbox_guid: Option<&str>,
582    ) -> Result<PolicyComplianceResult, PolicyError> {
583        let endpoint = if let Some(sandbox) = sandbox_guid {
584            format!(
585                "/appsec/v1/applications/{application_guid}/sandboxes/{sandbox}/policy/{policy_guid}/compliance"
586            )
587        } else {
588            format!("/appsec/v1/applications/{application_guid}/policy/{policy_guid}/compliance")
589        };
590
591        let response = self.client.get(&endpoint, None).await?;
592
593        let status = response.status().as_u16();
594        match status {
595            200 => {
596                let compliance: PolicyComplianceResult = response.json().await?;
597                Ok(compliance)
598            }
599            404 => Err(PolicyError::NotFound),
600            _ => {
601                let error_text = response.text().await.unwrap_or_default();
602                Err(PolicyError::Api(VeracodeError::InvalidResponse(format!(
603                    "HTTP {status}: {error_text}"
604                ))))
605            }
606        }
607    }
608
609    /// Initiate a policy scan for an application
610    ///
611    /// # Arguments
612    ///
613    /// * `request` - The policy scan request
614    ///
615    /// # Returns
616    ///
617    /// A `Result` containing the scan result or an error.
618    pub async fn initiate_policy_scan(
619        &self,
620        request: PolicyScanRequest,
621    ) -> Result<PolicyScanResult, PolicyError> {
622        let endpoint = "/appsec/v1/policy-scans";
623
624        let response = self.client.post(endpoint, Some(&request)).await?;
625
626        let status = response.status().as_u16();
627        match status {
628            200 | 201 => {
629                let scan_result: PolicyScanResult = response.json().await?;
630                Ok(scan_result)
631            }
632            400 => {
633                let error_text = response.text().await.unwrap_or_default();
634                Err(PolicyError::InvalidConfig(error_text))
635            }
636            404 => Err(PolicyError::NotFound),
637            _ => {
638                let error_text = response.text().await.unwrap_or_default();
639                Err(PolicyError::Api(VeracodeError::InvalidResponse(format!(
640                    "HTTP {status}: {error_text}"
641                ))))
642            }
643        }
644    }
645
646    /// Get policy scan status and results
647    ///
648    /// # Arguments
649    ///
650    /// * `scan_id` - The ID of the policy scan
651    ///
652    /// # Returns
653    ///
654    /// A `Result` containing the scan result or an error.
655    pub async fn get_policy_scan_result(
656        &self,
657        scan_id: u64,
658    ) -> Result<PolicyScanResult, PolicyError> {
659        let endpoint = format!("/appsec/v1/policy-scans/{scan_id}");
660
661        let response = self.client.get(&endpoint, None).await?;
662
663        let status = response.status().as_u16();
664        match status {
665            200 => {
666                let scan_result: PolicyScanResult = response.json().await?;
667                Ok(scan_result)
668            }
669            404 => Err(PolicyError::NotFound),
670            _ => {
671                let error_text = response.text().await.unwrap_or_default();
672                Err(PolicyError::Api(VeracodeError::InvalidResponse(format!(
673                    "HTTP {status}: {error_text}"
674                ))))
675            }
676        }
677    }
678
679    /// Check if a policy scan is complete
680    ///
681    /// # Arguments
682    ///
683    /// * `scan_id` - The ID of the policy scan
684    ///
685    /// # Returns
686    ///
687    /// A `Result` containing a boolean indicating completion status.
688    pub async fn is_policy_scan_complete(&self, scan_id: u64) -> Result<bool, PolicyError> {
689        let scan_result = self.get_policy_scan_result(scan_id).await?;
690        Ok(matches!(
691            scan_result.status,
692            ScanStatus::Completed | ScanStatus::Failed | ScanStatus::Cancelled
693        ))
694    }
695
696    /// Get policy violations for an application
697    ///
698    /// # Arguments
699    ///
700    /// * `application_guid` - The GUID of the application
701    /// * `policy_guid` - The GUID of the policy
702    /// * `sandbox_guid` - Optional sandbox GUID
703    ///
704    /// # Returns
705    ///
706    /// A `Result` containing policy violations or an error.
707    pub async fn get_policy_violations(
708        &self,
709        application_guid: &str,
710        policy_guid: &str,
711        sandbox_guid: Option<&str>,
712    ) -> Result<Vec<PolicyViolation>, PolicyError> {
713        let compliance = self
714            .evaluate_policy_compliance(application_guid, policy_guid, sandbox_guid)
715            .await?;
716        Ok(compliance.violations.unwrap_or_default())
717    }
718}
719
720/// Convenience methods for common policy operations
721impl<'a> PolicyApi<'a> {
722    /// Check if an application passes policy compliance
723    ///
724    /// # Arguments
725    ///
726    /// * `application_guid` - The GUID of the application
727    /// * `policy_guid` - The GUID of the policy
728    ///
729    /// # Returns
730    ///
731    /// A `Result` containing a boolean indicating compliance status.
732    pub async fn is_application_compliant(
733        &self,
734        application_guid: &str,
735        policy_guid: &str,
736    ) -> Result<bool, PolicyError> {
737        let compliance = self
738            .evaluate_policy_compliance(application_guid, policy_guid, None)
739            .await?;
740        Ok(compliance.passed)
741    }
742
743    /// Get compliance score for an application
744    ///
745    /// # Arguments
746    ///
747    /// * `application_guid` - The GUID of the application
748    /// * `policy_guid` - The GUID of the policy
749    ///
750    /// # Returns
751    ///
752    /// A `Result` containing the compliance score or an error.
753    pub async fn get_compliance_score(
754        &self,
755        application_guid: &str,
756        policy_guid: &str,
757    ) -> Result<Option<f64>, PolicyError> {
758        let compliance = self
759            .evaluate_policy_compliance(application_guid, policy_guid, None)
760            .await?;
761        Ok(compliance.score)
762    }
763
764    /// Get active policies for the organization
765    ///
766    /// # Returns
767    ///
768    /// A `Result` containing a list of active policies or an error.
769    pub async fn get_active_policies(&self) -> Result<Vec<SecurityPolicy>, PolicyError> {
770        // Note: The active/inactive concept may need to be handled differently
771        // based on the actual API response structure
772        let policies = self.list_policies(None).await?;
773        Ok(policies) // Return all policies for now
774    }
775}
776
777#[cfg(test)]
778mod tests {
779    use super::*;
780
781    #[test]
782    fn test_policy_list_params_to_query() {
783        let params = PolicyListParams {
784            name: Some("test-policy".to_string()),
785            is_active: Some(true),
786            page: Some(1),
787            size: Some(10),
788            ..Default::default()
789        };
790
791        let query_params = params.to_query_params();
792        assert_eq!(query_params.len(), 4);
793        assert!(query_params.contains(&("name".to_string(), "test-policy".to_string())));
794        assert!(query_params.contains(&("active".to_string(), "true".to_string())));
795        assert!(query_params.contains(&("page".to_string(), "1".to_string())));
796        assert!(query_params.contains(&("size".to_string(), "10".to_string())));
797    }
798
799    #[test]
800    fn test_policy_error_display() {
801        let error = PolicyError::NotFound;
802        assert_eq!(error.to_string(), "Policy not found");
803
804        let error = PolicyError::InvalidConfig("test".to_string());
805        assert_eq!(error.to_string(), "Invalid policy configuration: test");
806
807        let error = PolicyError::Timeout;
808        assert_eq!(error.to_string(), "Policy operation timed out");
809    }
810
811    #[test]
812    fn test_scan_type_serialization() {
813        let scan_type = ScanType::Static;
814        let json = serde_json::to_string(&scan_type).unwrap();
815        assert_eq!(json, "\"static\"");
816
817        let deserialized: ScanType = serde_json::from_str(&json).unwrap();
818        assert!(matches!(deserialized, ScanType::Static));
819    }
820
821    #[test]
822    fn test_policy_compliance_status_serialization() {
823        let status = PolicyComplianceStatus::Pass;
824        let json = serde_json::to_string(&status).unwrap();
825        assert_eq!(json, "\"PASS\"");
826
827        let deserialized: PolicyComplianceStatus = serde_json::from_str(&json).unwrap();
828        assert!(matches!(deserialized, PolicyComplianceStatus::Pass));
829    }
830}