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 provider_id: i64,
65 pub provider_type: String,
66 pub name: String,
67 pub status: PipelineStatus,
68 pub last_run: Option<DateTime<Utc>>,
69 pub last_updated: DateTime<Utc>,
70 pub repository: String,
71 pub branch: Option<String>,
72 pub workflow_file: Option<String>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub resource_state: Option<ResourceState>,
75 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
76 pub metadata: HashMap<String, serde_json::Value>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct PipelineRun {
81 pub id: String,
82 pub pipeline_id: String,
83 pub run_number: i64,
84 pub status: PipelineStatus,
85 pub started_at: DateTime<Utc>,
86 pub concluded_at: Option<DateTime<Utc>>,
87 pub duration_seconds: Option<i64>,
88 pub logs_url: String,
89 pub commit_sha: Option<String>,
90 pub commit_message: Option<String>,
91 pub branch: Option<String>,
92 pub actor: Option<String>,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub inputs: Option<serde_json::Value>,
95 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
96 pub metadata: HashMap<String, serde_json::Value>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct TriggerParams {
101 pub workflow_id: String,
102 pub inputs: Option<serde_json::Value>,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct BuildAgent {
107 pub id: String,
108 pub name: String,
109 pub hostname: String,
110 pub status: String,
111 pub job_id: Option<String>,
112 pub last_seen: DateTime<Utc>,
113 pub metadata: HashMap<String, String>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct BuildQueue {
118 pub id: String,
119 pub waiting: usize,
120 pub running: usize,
121 pub avg_wait_time: Option<i64>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct BuildArtifact {
126 pub id: String,
127 pub run_id: String,
128 pub filename: String,
129 pub size_bytes: i64,
130 pub download_url: String,
131 pub content_type: Option<String>,
132 pub created_at: DateTime<Utc>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct Organization {
137 pub id: String,
138 pub name: String,
139 pub description: Option<String>,
140}
141
142#[derive(Debug, Clone, Default, Serialize, Deserialize)]
143pub struct PipelineCatalog {
144 pub organizations: Vec<Organization>,
145 pub pipelines: Vec<AvailablePipeline>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
149#[serde(tag = "type", rename_all = "lowercase")]
150pub enum WorkflowParameterType {
151 String {
152 #[serde(default)]
153 default: Option<String>,
154 },
155 Boolean {
156 #[serde(default)]
157 default: bool,
158 },
159 Choice {
160 options: Vec<String>,
161 #[serde(default)]
162 default: Option<String>,
163 },
164 Number {
165 #[serde(default)]
166 default: Option<f64>,
167 },
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct WorkflowParameter {
172 pub name: String,
173 pub label: Option<String>,
174 pub description: Option<String>,
175 #[serde(flatten)]
176 pub param_type: WorkflowParameterType,
177 #[serde(default)]
178 pub required: bool,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct PaginationParams {
183 pub page: usize,
184 pub page_size: usize,
185}
186
187impl PaginationParams {
188 pub fn validate(&self) -> Result<(), String> {
189 if self.page == 0 {
190 return Err("Page must be >= 1".to_string());
191 }
192 if self.page_size == 0 {
193 return Err("Page size must be >= 1".to_string());
194 }
195 if self.page_size > 200 {
196 return Err("Page size must be <= 200".to_string());
197 }
198 Ok(())
199 }
200
201 pub fn calculate_offset(&self) -> Result<usize, String> {
202 self.page
203 .checked_sub(1)
204 .and_then(|p| p.checked_mul(self.page_size))
205 .ok_or_else(|| "Pagination offset overflow".to_string())
206 }
207}
208
209impl Default for PaginationParams {
210 fn default() -> Self {
211 Self {
212 page: 1,
213 page_size: 100,
214 }
215 }
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct PaginatedResponse<T> {
220 pub items: Vec<T>,
221 pub page: usize,
222 pub page_size: usize,
223 pub total_count: usize,
224 pub total_pages: usize,
225 pub has_more: bool,
226}
227
228impl<T> PaginatedResponse<T> {
229 pub fn new(items: Vec<T>, page: usize, page_size: usize, total_count: usize) -> Self {
230 let items_count = items.len();
231 let total_pages = if page_size > 0 {
232 total_count.div_ceil(page_size)
233 } else {
234 1
235 };
236 let has_more = items_count == page_size;
237
238 Self {
239 items,
240 page,
241 page_size,
242 total_count,
243 total_pages,
244 has_more,
245 }
246 }
247
248 pub fn empty() -> Self {
249 Self {
250 items: Vec::new(),
251 page: 1,
252 page_size: 100,
253 total_count: 0,
254 total_pages: 0,
255 has_more: false,
256 }
257 }
258}
259
260pub type PaginatedAvailablePipelines = PaginatedResponse<AvailablePipeline>;
261
262#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
263pub struct Permission {
264 pub name: String,
265 pub description: String,
266 pub required: bool,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct PermissionCheck {
271 pub permission: Permission,
272 pub granted: bool,
273}
274
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct PermissionStatus {
277 pub permissions: Vec<PermissionCheck>,
278 pub all_granted: bool,
279 pub checked_at: DateTime<Utc>,
280 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
281 pub metadata: HashMap<String, String>,
282}
283
284#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
285pub struct RateLimit {
286 pub limit: u64,
287 pub remaining: u64,
288 pub reset_at: DateTime<Utc>,
289}
290
291#[derive(Debug, Clone, Serialize, Deserialize)]
292pub struct Feature {
293 pub id: String,
294 pub name: String,
295 pub description: String,
296 pub required_permissions: Vec<String>,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct FeatureAvailability {
301 pub feature: Feature,
302 pub available: bool,
303 pub missing_permissions: Vec<String>,
304}