Skip to main content

coil_ops/reports/
definition.rs

1use crate::error::OpsModelError;
2use crate::identifiers::ReportId;
3use crate::validation::require_non_empty;
4use coil_auth::Capability;
5use coil_core::{
6    ReportDefinition as ManifestReportDefinition, ReportDeliveryMode as ManifestReportDeliveryMode,
7    ReportFormat as ManifestReportFormat, ReportSensitivity as ManifestReportSensitivity,
8};
9use coil_jobs::RetryPolicy;
10use std::fmt;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ReportFormat {
14    Csv,
15    Json,
16    Pdf,
17}
18
19impl ReportFormat {
20    pub fn extension(self) -> &'static str {
21        match self {
22            Self::Csv => "csv",
23            Self::Json => "json",
24            Self::Pdf => "pdf",
25        }
26    }
27}
28
29impl fmt::Display for ReportFormat {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::Csv => f.write_str("csv"),
33            Self::Json => f.write_str("json"),
34            Self::Pdf => f.write_str("pdf"),
35        }
36    }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum ReportSensitivity {
41    Public,
42    Internal,
43    Restricted,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum ReportDeliveryMode {
48    PublicObjectStore,
49    SignedUrl,
50    InternalOnly,
51}
52
53impl ReportDeliveryMode {
54    pub fn is_public(self) -> bool {
55        matches!(self, Self::PublicObjectStore)
56    }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct ReportDefinition {
61    pub id: ReportId,
62    pub source_module: String,
63    pub title: String,
64    pub description: Option<String>,
65    pub required_capability: Capability,
66    pub format: ReportFormat,
67    pub sensitivity: ReportSensitivity,
68    pub delivery_mode: ReportDeliveryMode,
69    pub export_prefix: String,
70    pub retry_policy: RetryPolicy,
71}
72
73impl ReportDefinition {
74    pub fn new(
75        id: ReportId,
76        source_module: impl Into<String>,
77        title: impl Into<String>,
78        description: Option<String>,
79        required_capability: Capability,
80        format: ReportFormat,
81        sensitivity: ReportSensitivity,
82        delivery_mode: ReportDeliveryMode,
83        export_prefix: impl Into<String>,
84        retry_policy: RetryPolicy,
85    ) -> Result<Self, OpsModelError> {
86        let source_module = require_non_empty("report_source_module", source_module.into())?;
87        let title = require_non_empty("report_title", title.into())?;
88        let export_prefix = require_non_empty("report_export_prefix", export_prefix.into())?;
89
90        if matches!(
91            (sensitivity, delivery_mode),
92            (ReportSensitivity::Public, ReportDeliveryMode::InternalOnly)
93        ) {
94            return Err(OpsModelError::InvalidReportDelivery {
95                report_id: id.to_string(),
96                reason:
97                    "public reports should be delivered through a public object store or signed URL"
98                        .to_string(),
99            });
100        }
101
102        if matches!(sensitivity, ReportSensitivity::Restricted)
103            && matches!(delivery_mode, ReportDeliveryMode::PublicObjectStore)
104        {
105            return Err(OpsModelError::InvalidReportDelivery {
106                report_id: id.to_string(),
107                reason: "restricted reports cannot be delivered through a public object store"
108                    .to_string(),
109            });
110        }
111
112        Ok(Self {
113            id,
114            source_module,
115            title,
116            description,
117            required_capability,
118            format,
119            sensitivity,
120            delivery_mode,
121            export_prefix,
122            retry_policy,
123        })
124    }
125
126    pub fn allows(&self, capabilities: &[Capability]) -> bool {
127        capabilities.contains(&self.required_capability)
128    }
129
130    pub(crate) fn from_manifest_definition(
131        source_module: &str,
132        definition: &ManifestReportDefinition,
133    ) -> Result<Self, OpsModelError> {
134        Self::new(
135            ReportId::new(definition.id.clone())?,
136            source_module,
137            definition.title.clone(),
138            definition.description.clone(),
139            definition.required_capability,
140            map_report_format(definition.format),
141            map_report_sensitivity(definition.sensitivity),
142            map_report_delivery_mode(definition.delivery_mode),
143            definition.export_prefix.clone(),
144            definition.retry_policy.clone(),
145        )
146    }
147}
148
149pub(crate) fn map_report_format(format: ManifestReportFormat) -> ReportFormat {
150    match format {
151        ManifestReportFormat::Csv => ReportFormat::Csv,
152        ManifestReportFormat::Json => ReportFormat::Json,
153        ManifestReportFormat::Pdf => ReportFormat::Pdf,
154    }
155}
156
157pub(crate) fn map_report_sensitivity(sensitivity: ManifestReportSensitivity) -> ReportSensitivity {
158    match sensitivity {
159        ManifestReportSensitivity::Public => ReportSensitivity::Public,
160        ManifestReportSensitivity::Internal => ReportSensitivity::Internal,
161        ManifestReportSensitivity::Restricted => ReportSensitivity::Restricted,
162    }
163}
164
165pub(crate) fn map_report_delivery_mode(mode: ManifestReportDeliveryMode) -> ReportDeliveryMode {
166    match mode {
167        ManifestReportDeliveryMode::PublicObjectStore => ReportDeliveryMode::PublicObjectStore,
168        ManifestReportDeliveryMode::SignedUrl => ReportDeliveryMode::SignedUrl,
169        ManifestReportDeliveryMode::InternalOnly => ReportDeliveryMode::InternalOnly,
170    }
171}