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