Skip to main content

pipedash_plugin_api/
types.rs

1use std::collections::HashMap;
2
3use chrono::{
4    DateTime,
5    Utc,
6};
7use serde::{
8    Deserialize,
9    Serialize,
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum PipelineStatus {
15    Success,
16    Failed,
17    Degraded,
18    Running,
19    Pending,
20    Cancelled,
21    Skipped,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum ReviewDecision {
27    Approve,
28    Reject,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct AvailablePipeline {
33    pub id: String,
34    pub name: String,
35    pub description: Option<String>,
36    pub organization: Option<String>,
37    pub repository: Option<String>,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct ResourceState {
42    pub label: String,
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub message: Option<String>,
45}
46
47impl ResourceState {
48    pub fn new(label: impl Into<String>, message: Option<&str>) -> Self {
49        Self {
50            label: label.into(),
51            message: message
52                .map(str::trim)
53                .filter(|message| !message.is_empty())
54                .map(str::to_string),
55        }
56    }
57}
58
59pub const READ_ONLY_METADATA_KEY: &str = "read_only";
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Pipeline {
63    pub id: String,
64    pub name: String,
65    pub status: PipelineStatus,
66    pub last_run: Option<DateTime<Utc>>,
67    pub last_updated: DateTime<Utc>,
68    pub repository: String,
69    pub branch: Option<String>,
70    pub workflow_file: Option<String>,
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub resource_state: Option<ResourceState>,
73    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
74    pub metadata: HashMap<String, serde_json::Value>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct PipelineRun {
79    pub id: String,
80    pub pipeline_id: String,
81    pub run_number: i64,
82    pub status: PipelineStatus,
83    pub started_at: DateTime<Utc>,
84    pub concluded_at: Option<DateTime<Utc>>,
85    pub duration_seconds: Option<i64>,
86    pub logs_url: String,
87    pub commit_sha: Option<String>,
88    pub commit_message: Option<String>,
89    pub branch: Option<String>,
90    pub actor: Option<String>,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub inputs: Option<serde_json::Value>,
93    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
94    pub metadata: HashMap<String, serde_json::Value>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct TriggerParams {
99    pub workflow_id: String,
100    pub inputs: Option<serde_json::Value>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct Organization {
105    pub id: String,
106    pub name: String,
107    pub description: Option<String>,
108}
109
110#[derive(Debug, Clone, Default, Serialize, Deserialize)]
111pub struct PipelineCatalog {
112    pub organizations: Vec<Organization>,
113    pub pipelines: Vec<AvailablePipeline>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(tag = "type", rename_all = "lowercase")]
118pub enum WorkflowParameterType {
119    String {
120        #[serde(default)]
121        default: Option<String>,
122    },
123    Boolean {
124        #[serde(default)]
125        default: bool,
126    },
127    Choice {
128        options: Vec<String>,
129        #[serde(default)]
130        default: Option<String>,
131    },
132    Number {
133        #[serde(default)]
134        default: Option<f64>,
135    },
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct WorkflowParameter {
140    pub name: String,
141    pub label: Option<String>,
142    pub description: Option<String>,
143    #[serde(flatten)]
144    pub param_type: WorkflowParameterType,
145    #[serde(default)]
146    pub required: bool,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct PaginationParams {
151    pub page: usize,
152    pub page_size: usize,
153}
154
155impl PaginationParams {
156    pub fn validate(&self) -> Result<(), String> {
157        if self.page == 0 {
158            return Err("Page must be >= 1".to_string());
159        }
160        if self.page_size == 0 {
161            return Err("Page size must be >= 1".to_string());
162        }
163        if self.page_size > 200 {
164            return Err("Page size must be <= 200".to_string());
165        }
166        Ok(())
167    }
168
169    pub fn calculate_offset(&self) -> Result<usize, String> {
170        self.page
171            .checked_sub(1)
172            .and_then(|p| p.checked_mul(self.page_size))
173            .ok_or_else(|| "Pagination offset overflow".to_string())
174    }
175}
176
177impl Default for PaginationParams {
178    fn default() -> Self {
179        Self {
180            page: 1,
181            page_size: 100,
182        }
183    }
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct PaginatedResponse<T> {
188    pub items: Vec<T>,
189    pub page: usize,
190    pub page_size: usize,
191    pub total_count: usize,
192    pub total_pages: usize,
193    pub has_more: bool,
194}
195
196impl<T> PaginatedResponse<T> {
197    pub fn new(items: Vec<T>, page: usize, page_size: usize, total_count: usize) -> Self {
198        let items_count = items.len();
199        let total_pages = if page_size > 0 {
200            total_count.div_ceil(page_size)
201        } else {
202            1
203        };
204        let has_more = items_count == page_size;
205
206        Self {
207            items,
208            page,
209            page_size,
210            total_count,
211            total_pages,
212            has_more,
213        }
214    }
215
216    pub fn empty() -> Self {
217        Self {
218            items: Vec::new(),
219            page: 1,
220            page_size: 100,
221            total_count: 0,
222            total_pages: 0,
223            has_more: false,
224        }
225    }
226}
227
228pub type PaginatedAvailablePipelines = PaginatedResponse<AvailablePipeline>;
229
230#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
231pub struct Permission {
232    pub name: String,
233    pub description: String,
234    pub required: bool,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct PermissionCheck {
239    pub permission: Permission,
240    pub granted: bool,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct PermissionStatus {
245    pub permissions: Vec<PermissionCheck>,
246    pub all_granted: bool,
247    pub checked_at: DateTime<Utc>,
248    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
249    pub metadata: HashMap<String, String>,
250    #[serde(default)]
251    pub credential_label: Option<String>,
252}
253
254impl PermissionStatus {
255    pub fn evaluate(
256        permissions: &[Permission], token_type: &str, mut granted: impl FnMut(&Permission) -> bool,
257    ) -> Self {
258        let permissions: Vec<_> = permissions
259            .iter()
260            .map(|permission| PermissionCheck {
261                granted: granted(permission),
262                permission: permission.clone(),
263            })
264            .collect();
265        let all_granted = permissions
266            .iter()
267            .filter(|check| check.permission.required)
268            .all(|check| check.granted);
269
270        Self {
271            permissions,
272            all_granted,
273            checked_at: Utc::now(),
274            metadata: HashMap::from([("token_type".to_string(), token_type.to_string())]),
275            credential_label: None,
276        }
277    }
278}
279
280#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
281pub struct RateLimit {
282    pub limit: u64,
283    pub remaining: u64,
284    pub reset_at: DateTime<Utc>,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct Feature {
289    pub id: String,
290    pub name: String,
291    pub description: String,
292    pub required_permissions: Vec<String>,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct FeatureAvailability {
297    pub feature: Feature,
298    pub available: bool,
299    pub missing_permissions: Vec<String>,
300}
301
302#[cfg(test)]
303mod tests;