1use std::{fmt::Display, str::FromStr};
2
3use serde::Deserialize;
4
5use crate::{
6 cli::browse::BrowseOptions,
7 cmds::{
8 cicd::{
9 Job, JobListBodyArgs, LintResponse, Pipeline, PipelineBodyArgs, Runner,
10 RunnerListBodyArgs, RunnerMetadata, RunnerPostDataCliArgs, RunnerRegistrationResponse,
11 YamlBytes,
12 },
13 docker::{DockerListBodyArgs, ImageMetadata, RegistryRepository, RepositoryTag},
14 gist::{Gist, GistListBodyArgs},
15 merge_request::{
16 Comment, CommentMergeRequestBodyArgs, CommentMergeRequestListBodyArgs,
17 MergeRequestBodyArgs, MergeRequestListBodyArgs, MergeRequestResponse,
18 },
19 project::{Member, Project, ProjectListBodyArgs, Tag},
20 release::{Release, ReleaseAssetListBodyArgs, ReleaseAssetMetadata, ReleaseBodyArgs},
21 trending::TrendingProject,
22 user::UserCliArgs,
23 },
24 io::CmdInfo,
25 Result,
26};
27
28pub trait MergeRequest {
29 fn open(&self, args: MergeRequestBodyArgs) -> Result<MergeRequestResponse>;
30 fn list(&self, args: MergeRequestListBodyArgs) -> Result<Vec<MergeRequestResponse>>;
31 fn merge(&self, id: i64) -> Result<MergeRequestResponse>;
32 fn get(&self, id: i64) -> Result<MergeRequestResponse>;
33 fn close(&self, id: i64) -> Result<MergeRequestResponse>;
34 fn approve(&self, id: i64) -> Result<MergeRequestResponse>;
35 fn num_pages(&self, args: MergeRequestListBodyArgs) -> Result<Option<u32>>;
38 fn num_resources(&self, args: MergeRequestListBodyArgs) -> Result<Option<NumberDeltaErr>>;
39}
40
41pub trait RemoteProject {
42 fn get_project_data(&self, id: Option<i64>, path: Option<&str>) -> Result<CmdInfo>;
46 fn get_project_members(&self) -> Result<CmdInfo>;
47 fn get_url(&self, option: BrowseOptions) -> String;
50 fn list(&self, args: ProjectListBodyArgs) -> Result<Vec<Project>>;
51 fn num_pages(&self, args: ProjectListBodyArgs) -> Result<Option<u32>>;
52 fn num_resources(&self, args: ProjectListBodyArgs) -> Result<Option<NumberDeltaErr>>;
53}
54
55pub trait RemoteTag: RemoteProject {
56 fn list(&self, args: ProjectListBodyArgs) -> Result<Vec<Tag>>;
57}
58
59pub trait ProjectMember: RemoteProject {
60 fn list(&self, args: ProjectListBodyArgs) -> Result<Vec<Member>>;
61}
62
63pub trait Cicd {
64 fn list(&self, args: PipelineBodyArgs) -> Result<Vec<Pipeline>>;
65 fn get_pipeline(&self, id: i64) -> Result<Pipeline>;
66 fn num_pages(&self) -> Result<Option<u32>>;
67 fn num_resources(&self) -> Result<Option<NumberDeltaErr>>;
68 fn lint(&self, body: YamlBytes) -> Result<LintResponse>;
71}
72
73pub trait CicdRunner {
74 fn list(&self, args: RunnerListBodyArgs) -> Result<Vec<Runner>>;
75 fn get(&self, id: i64) -> Result<RunnerMetadata>;
76 fn create(&self, args: RunnerPostDataCliArgs) -> Result<RunnerRegistrationResponse>;
77 fn num_pages(&self, args: RunnerListBodyArgs) -> Result<Option<u32>>;
78 fn num_resources(&self, args: RunnerListBodyArgs) -> Result<Option<NumberDeltaErr>>;
79}
80
81pub trait CicdJob {
82 fn list(&self, args: JobListBodyArgs) -> Result<Vec<Job>>;
83 fn num_pages(&self, args: JobListBodyArgs) -> Result<Option<u32>>;
84 fn num_resources(&self, args: JobListBodyArgs) -> Result<Option<NumberDeltaErr>>;
85}
86
87pub trait Deploy {
88 fn list(&self, args: ReleaseBodyArgs) -> Result<Vec<Release>>;
89 fn num_pages(&self) -> Result<Option<u32>>;
90 fn num_resources(&self) -> Result<Option<NumberDeltaErr>>;
91}
92
93pub trait DeployAsset {
94 fn list(&self, args: ReleaseAssetListBodyArgs) -> Result<Vec<ReleaseAssetMetadata>>;
95 fn num_pages(&self, args: ReleaseAssetListBodyArgs) -> Result<Option<u32>>;
96 fn num_resources(&self, args: ReleaseAssetListBodyArgs) -> Result<Option<NumberDeltaErr>>;
97}
98
99pub trait UserInfo {
100 fn get_auth_user(&self) -> Result<Member>;
102 fn get(&self, args: &UserCliArgs) -> Result<Member>;
103}
104
105pub trait CodeGist {
106 fn list(&self, args: GistListBodyArgs) -> Result<Vec<Gist>>;
107 fn num_pages(&self) -> Result<Option<u32>>;
108 fn num_resources(&self) -> Result<Option<NumberDeltaErr>>;
109}
110
111pub trait Timestamp {
112 fn created_at(&self) -> String;
113}
114
115pub trait ContainerRegistry {
116 fn list_repositories(&self, args: DockerListBodyArgs) -> Result<Vec<RegistryRepository>>;
117 fn list_repository_tags(&self, args: DockerListBodyArgs) -> Result<Vec<RepositoryTag>>;
118 fn num_pages_repository_tags(&self, repository_id: i64) -> Result<Option<u32>>;
119 fn num_resources_repository_tags(&self, repository_id: i64) -> Result<Option<NumberDeltaErr>>;
120 fn num_pages_repositories(&self) -> Result<Option<u32>>;
121 fn num_resources_repositories(&self) -> Result<Option<NumberDeltaErr>>;
122 fn get_image_metadata(&self, repository_id: i64, tag: &str) -> Result<ImageMetadata>;
123}
124
125pub trait CommentMergeRequest {
126 fn create(&self, args: CommentMergeRequestBodyArgs) -> Result<()>;
127 fn list(&self, args: CommentMergeRequestListBodyArgs) -> Result<Vec<Comment>>;
128 fn num_pages(&self, args: CommentMergeRequestListBodyArgs) -> Result<Option<u32>>;
129 fn num_resources(
130 &self,
131 args: CommentMergeRequestListBodyArgs,
132 ) -> Result<Option<NumberDeltaErr>>;
133}
134
135pub trait TrendingProjectURL {
136 fn list(&self, language: String) -> Result<Vec<TrendingProject>>;
137}
138
139pub struct NumberDeltaErr {
145 pub num: u32,
147 pub delta: u32,
149}
150
151impl NumberDeltaErr {
152 pub fn new(num: u32, delta: u32) -> Self {
153 Self { num, delta }
154 }
155
156 fn compute_interval(&self) -> (u32, u32) {
157 if self.num < self.delta {
158 return (1, self.delta);
159 }
160 (self.num - self.delta + 1, self.num)
161 }
162}
163
164impl Display for NumberDeltaErr {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 let (start, end) = self.compute_interval();
167 write!(f, "({start}, {end})")
168 }
169}
170
171#[derive(Clone, Debug, PartialEq, Hash, Eq)]
177pub enum ApiOperation {
178 MergeRequest,
179 Pipeline,
180 Project,
183 ContainerRegistry,
184 Release,
185 SinglePage,
187 Gist,
189 RepositoryTag,
190}
191
192impl Display for ApiOperation {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 match self {
195 ApiOperation::MergeRequest => write!(f, "merge_request"),
196 ApiOperation::Pipeline => write!(f, "pipeline"),
197 ApiOperation::Project => write!(f, "project"),
198 ApiOperation::ContainerRegistry => write!(f, "container_registry"),
199 ApiOperation::Release => write!(f, "release"),
200 ApiOperation::SinglePage => write!(f, "single_page"),
201 ApiOperation::Gist => write!(f, "gist"),
202 ApiOperation::RepositoryTag => write!(f, "repository_tag"),
203 }
204 }
205}
206
207impl<'de> Deserialize<'de> for ApiOperation {
208 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
209 where
210 D: serde::Deserializer<'de>,
211 {
212 let s = String::deserialize(deserializer)?;
213 ApiOperation::from_str(&s).map_err(serde::de::Error::custom)
214 }
215}
216
217impl FromStr for ApiOperation {
218 type Err = String;
219
220 fn from_str(s: &str) -> std::result::Result<ApiOperation, std::string::String> {
221 match s.to_lowercase().as_str() {
222 "merge_request" => Ok(ApiOperation::MergeRequest),
223 "pipeline" => Ok(ApiOperation::Pipeline),
224 "project" => Ok(ApiOperation::Project),
225 "container_registry" => Ok(ApiOperation::ContainerRegistry),
226 "release" => Ok(ApiOperation::Release),
227 "single_page" => Ok(ApiOperation::SinglePage),
228 "gist" => Ok(ApiOperation::Gist),
229 "repository_tag" => Ok(ApiOperation::RepositoryTag),
230 _ => Err(format!("Unknown ApiOperation: {s}")),
231 }
232 }
233}
234
235pub struct ApiOperationIterator {
236 current: Option<ApiOperation>,
237}
238
239impl ApiOperationIterator {
240 fn new() -> Self {
241 ApiOperationIterator { current: None }
242 }
243}
244
245impl Iterator for ApiOperationIterator {
246 type Item = ApiOperation;
247
248 fn next(&mut self) -> Option<Self::Item> {
249 let next = match self.current {
250 None => Some(ApiOperation::MergeRequest),
251 Some(ApiOperation::MergeRequest) => Some(ApiOperation::Pipeline),
252 Some(ApiOperation::Pipeline) => Some(ApiOperation::Project),
253 Some(ApiOperation::Project) => Some(ApiOperation::ContainerRegistry),
254 Some(ApiOperation::ContainerRegistry) => Some(ApiOperation::Release),
255 Some(ApiOperation::Release) => Some(ApiOperation::SinglePage),
256 Some(ApiOperation::SinglePage) => Some(ApiOperation::Gist),
257 Some(ApiOperation::Gist) => Some(ApiOperation::RepositoryTag),
258 Some(ApiOperation::RepositoryTag) => None,
259 };
260 self.current = next.clone();
261 next
262 }
263}
264
265impl ApiOperation {
266 pub fn iter() -> ApiOperationIterator {
267 ApiOperationIterator::new()
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_api_operation_display() {
277 assert_eq!(format!("{}", ApiOperation::MergeRequest), "merge_request");
278 assert_eq!(format!("{}", ApiOperation::Pipeline), "pipeline");
279 assert_eq!(format!("{}", ApiOperation::Project), "project");
280 assert_eq!(
281 format!("{}", ApiOperation::ContainerRegistry),
282 "container_registry"
283 );
284 assert_eq!(format!("{}", ApiOperation::Release), "release");
285 assert_eq!(format!("{}", ApiOperation::SinglePage), "single_page");
286 }
287
288 #[test]
289 fn test_delta_err_display() {
290 let delta_err = NumberDeltaErr::new(40, 20);
291 assert_eq!("(21, 40)", delta_err.to_string());
292 }
293
294 #[test]
295 fn test_num_less_than_delta_begins_at_one_up_to_delta() {
296 let delta_err = NumberDeltaErr::new(25, 30);
297 assert_eq!("(1, 30)", delta_err.to_string());
298 }
299
300 #[test]
301 fn test_api_operation_iterator() {
302 let operations: Vec<ApiOperation> = ApiOperation::iter().collect();
303 assert_eq!(operations.len(), 8);
304 assert_eq!(operations[0], ApiOperation::MergeRequest);
305 assert_eq!(operations[7], ApiOperation::RepositoryTag);
306 }
307}