gitlab/api/projects/pipelines/
pipelines.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7use chrono::{DateTime, Utc};
8use derive_builder::Builder;
9
10use crate::api::common::{NameOrId, SortOrder};
11use crate::api::endpoint_prelude::*;
12use crate::api::ParamValue;
13
14/// Scopes for pipelines.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum PipelineScope {
18    /// Currently running.
19    Running,
20    /// Created, but blocked on available runners or triggers.
21    Pending,
22    /// Completed pipelines.
23    Finished,
24    /// Pipelines for branches.
25    Branches,
26    /// Pipelines for tags.
27    Tags,
28}
29
30impl PipelineScope {
31    /// The scope as a query parameter.
32    fn as_str(self) -> &'static str {
33        match self {
34            PipelineScope::Running => "running",
35            PipelineScope::Pending => "pending",
36            PipelineScope::Finished => "finished",
37            PipelineScope::Branches => "branches",
38            PipelineScope::Tags => "tags",
39        }
40    }
41}
42
43impl ParamValue<'static> for PipelineScope {
44    fn as_value(&self) -> Cow<'static, str> {
45        self.as_str().into()
46    }
47}
48
49/// The status of a pipeline.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[non_exhaustive]
52pub enum PipelineStatus {
53    /// Currently running.
54    Running,
55    /// Ready to run, but no jobs have been claimed by a runner.
56    Pending,
57    /// Successfully completed.
58    Success,
59    /// Unsuccessfully completed.
60    Failed,
61    /// Canceled.
62    Canceled,
63    /// Skipped.
64    Skipped,
65    /// Created, but blocked on available runners or triggers.
66    Created,
67    /// Awaiting manual triggering.
68    Manual,
69    /// Pipelines which have been scheduled.
70    Scheduled,
71    /// Pipelines which are being prepared.
72    Preparing,
73    /// Pipelines waiting for a resource.
74    WaitingForResource,
75}
76
77impl PipelineStatus {
78    /// The status as a query parameter.
79    fn as_str(self) -> &'static str {
80        match self {
81            PipelineStatus::Running => "running",
82            PipelineStatus::Pending => "pending",
83            PipelineStatus::Success => "success",
84            PipelineStatus::Failed => "failed",
85            PipelineStatus::Canceled => "canceled",
86            PipelineStatus::Skipped => "skipped",
87            PipelineStatus::Created => "created",
88            PipelineStatus::Manual => "manual",
89            PipelineStatus::Scheduled => "scheduled",
90            PipelineStatus::Preparing => "preparing",
91            PipelineStatus::WaitingForResource => "waiting_for_resource",
92        }
93    }
94}
95
96impl ParamValue<'static> for PipelineStatus {
97    fn as_value(&self) -> Cow<'static, str> {
98        self.as_str().into()
99    }
100}
101
102/// Keys pipeline results may be ordered by.
103#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
104#[non_exhaustive]
105pub enum PipelineOrderBy {
106    /// Order by the pipeline ID.
107    #[default]
108    Id,
109    /// Order by the status of the pipeline.
110    Status,
111    /// Order by the ref the pipeline was triggered for.
112    Ref,
113    /// When the pipeline was last updated.
114    UpdatedAt,
115    /// The ID of the user that created the pipeline.
116    UserId,
117}
118
119impl PipelineOrderBy {
120    /// The ordering as a query parameter.
121    fn as_str(self) -> &'static str {
122        match self {
123            PipelineOrderBy::Id => "id",
124            PipelineOrderBy::Status => "status",
125            PipelineOrderBy::Ref => "ref",
126            PipelineOrderBy::UpdatedAt => "updated_at",
127            PipelineOrderBy::UserId => "user_id",
128        }
129    }
130}
131
132impl ParamValue<'static> for PipelineOrderBy {
133    fn as_value(&self) -> Cow<'static, str> {
134        self.as_str().into()
135    }
136}
137
138/// Ways that pipelines can be created.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140#[non_exhaustive]
141pub enum PipelineSource {
142    /// A pipeline created by pushing to a repository.
143    Push,
144    /// A pipeline created through the web interface.
145    Web,
146    /// A pipeline created by a trigger.
147    Trigger,
148    /// A pipeline created on a schedule.
149    Schedule,
150    /// A pipeline created through the API.
151    Api,
152    /// A pipeline created externally.
153    External,
154    /// A pipeline created by another pipeline.
155    Pipeline,
156    /// A pipeline created through a chat.
157    Chat,
158    /// A pipeline created through the web IDE.
159    WebIde,
160    /// A pipeline created by a merge request event.
161    MergeRequestEvent,
162    /// A pipeline created by an external pull request event.
163    ExternalPullRequestEvent,
164    /// A pipeline created by a parent pipeline.
165    ParentPipeline,
166    /// A pipeline created by an on-demand DAST scan.
167    OnDemandDastScan,
168    /// A pipeline created by an on-demand DAST validation.
169    OnDemandDastValidation,
170    /// A pipeline created by a security orchestration policy.
171    SecurityOrchestrationPolicy,
172}
173
174impl PipelineSource {
175    /// The ordering as a query parameter.
176    fn as_str(self) -> &'static str {
177        match self {
178            PipelineSource::Push => "push",
179            PipelineSource::Web => "web",
180            PipelineSource::Trigger => "trigger",
181            PipelineSource::Schedule => "schedule",
182            PipelineSource::Api => "api",
183            PipelineSource::External => "external",
184            PipelineSource::Pipeline => "pipeline",
185            PipelineSource::Chat => "chat",
186            PipelineSource::WebIde => "web_ide",
187            PipelineSource::MergeRequestEvent => "merge_request_event",
188            PipelineSource::ExternalPullRequestEvent => "external_pull_request_event",
189            PipelineSource::ParentPipeline => "parent_pipeline",
190            PipelineSource::OnDemandDastScan => "ondemand_dast_scan",
191            PipelineSource::OnDemandDastValidation => "ondemand_dast_validation",
192            PipelineSource::SecurityOrchestrationPolicy => "security_orchestration_policy",
193        }
194    }
195}
196
197impl ParamValue<'static> for PipelineSource {
198    fn as_value(&self) -> Cow<'static, str> {
199        self.as_str().into()
200    }
201}
202
203/// Query for pipelines within a project.
204#[derive(Debug, Builder, Clone)]
205#[builder(setter(strip_option))]
206pub struct Pipelines<'a> {
207    /// The project to query for pipelines.
208    #[builder(setter(into))]
209    project: NameOrId<'a>,
210
211    /// Filter pipelines by its scope.
212    #[builder(default)]
213    scope: Option<PipelineScope>,
214    /// Filter pipelines by its status.
215    #[builder(default)]
216    status: Option<PipelineStatus>,
217    /// Filter pipelines by the owning ref.
218    #[builder(setter(into), default)]
219    ref_: Option<Cow<'a, str>>,
220    /// Filter pipelines for a given commit SHA.
221    #[builder(setter(into), default)]
222    sha: Option<Cow<'a, str>>,
223    /// Filter pipelines with or without YAML errors.
224    #[builder(default)]
225    yaml_errors: Option<bool>,
226    /// Filter pipelines by the username of the triggering user.
227    #[builder(setter(into), default)]
228    username: Option<Cow<'a, str>>,
229
230    /// Order results by a given key.
231    #[builder(default)]
232    order_by: Option<PipelineOrderBy>,
233    /// Sort order for resulting pipelines.
234    #[builder(default)]
235    sort: Option<SortOrder>,
236
237    /// Filter pipelines by the last updated date before this time.
238    #[builder(default)]
239    updated_before: Option<DateTime<Utc>>,
240    /// Filter pipelines by the last updated date after this time.
241    #[builder(default)]
242    updated_after: Option<DateTime<Utc>>,
243    /// How the pipeline was triggered.
244    #[builder(default)]
245    source: Option<PipelineSource>,
246}
247
248impl<'a> Pipelines<'a> {
249    /// Create a builder for the endpoint.
250    pub fn builder() -> PipelinesBuilder<'a> {
251        PipelinesBuilder::default()
252    }
253}
254
255impl Endpoint for Pipelines<'_> {
256    fn method(&self) -> Method {
257        Method::GET
258    }
259
260    fn endpoint(&self) -> Cow<'static, str> {
261        format!("projects/{}/pipelines", self.project).into()
262    }
263
264    fn parameters(&self) -> QueryParams {
265        let mut params = QueryParams::default();
266
267        params
268            .push_opt("scope", self.scope)
269            .push_opt("status", self.status)
270            .push_opt("ref", self.ref_.as_ref())
271            .push_opt("sha", self.sha.as_ref())
272            .push_opt("yaml_errors", self.yaml_errors)
273            .push_opt("username", self.username.as_ref())
274            .push_opt("updated_after", self.updated_after)
275            .push_opt("updated_before", self.updated_before)
276            .push_opt("source", self.source)
277            .push_opt("order_by", self.order_by)
278            .push_opt("sort", self.sort);
279
280        params
281    }
282}
283
284impl Pageable for Pipelines<'_> {}
285
286#[cfg(test)]
287mod tests {
288    use chrono::{TimeZone, Utc};
289
290    use crate::api::common::SortOrder;
291    use crate::api::projects::pipelines::{
292        PipelineOrderBy, PipelineScope, PipelineSource, PipelineStatus, Pipelines,
293        PipelinesBuilderError,
294    };
295    use crate::api::{self, Query};
296    use crate::test::client::{ExpectedUrl, SingleTestClient};
297
298    #[test]
299    fn pipeline_scope_as_str() {
300        let items = &[
301            (PipelineScope::Running, "running"),
302            (PipelineScope::Pending, "pending"),
303            (PipelineScope::Finished, "finished"),
304            (PipelineScope::Branches, "branches"),
305            (PipelineScope::Tags, "tags"),
306        ];
307
308        for (i, s) in items {
309            assert_eq!(i.as_str(), *s);
310        }
311    }
312
313    #[test]
314    fn pipeline_status_as_str() {
315        let items = &[
316            (PipelineStatus::Running, "running"),
317            (PipelineStatus::Pending, "pending"),
318            (PipelineStatus::Success, "success"),
319            (PipelineStatus::Failed, "failed"),
320            (PipelineStatus::Canceled, "canceled"),
321            (PipelineStatus::Skipped, "skipped"),
322            (PipelineStatus::Created, "created"),
323            (PipelineStatus::Manual, "manual"),
324            (PipelineStatus::Scheduled, "scheduled"),
325            (PipelineStatus::Preparing, "preparing"),
326            (PipelineStatus::WaitingForResource, "waiting_for_resource"),
327        ];
328
329        for (i, s) in items {
330            assert_eq!(i.as_str(), *s);
331        }
332    }
333
334    #[test]
335    fn order_by_default() {
336        assert_eq!(PipelineOrderBy::default(), PipelineOrderBy::Id);
337    }
338
339    #[test]
340    fn order_by_as_str() {
341        let items = &[
342            (PipelineOrderBy::Id, "id"),
343            (PipelineOrderBy::Status, "status"),
344            (PipelineOrderBy::Ref, "ref"),
345            (PipelineOrderBy::UpdatedAt, "updated_at"),
346            (PipelineOrderBy::UserId, "user_id"),
347        ];
348
349        for (i, s) in items {
350            assert_eq!(i.as_str(), *s);
351        }
352    }
353
354    #[test]
355    fn pipeline_source_as_str() {
356        let items = &[
357            (PipelineSource::Push, "push"),
358            (PipelineSource::Web, "web"),
359            (PipelineSource::Trigger, "trigger"),
360            (PipelineSource::Schedule, "schedule"),
361            (PipelineSource::Api, "api"),
362            (PipelineSource::External, "external"),
363            (PipelineSource::Pipeline, "pipeline"),
364            (PipelineSource::Chat, "chat"),
365            (PipelineSource::WebIde, "web_ide"),
366            (PipelineSource::MergeRequestEvent, "merge_request_event"),
367            (
368                PipelineSource::ExternalPullRequestEvent,
369                "external_pull_request_event",
370            ),
371            (PipelineSource::ParentPipeline, "parent_pipeline"),
372            (PipelineSource::OnDemandDastScan, "ondemand_dast_scan"),
373            (
374                PipelineSource::OnDemandDastValidation,
375                "ondemand_dast_validation",
376            ),
377            (
378                PipelineSource::SecurityOrchestrationPolicy,
379                "security_orchestration_policy",
380            ),
381        ];
382
383        for (i, s) in items {
384            assert_eq!(i.as_str(), *s);
385        }
386    }
387
388    #[test]
389    fn project_is_needed() {
390        let err = Pipelines::builder().build().unwrap_err();
391        crate::test::assert_missing_field!(err, PipelinesBuilderError, "project");
392    }
393
394    #[test]
395    fn project_is_sufficient() {
396        Pipelines::builder().project(1).build().unwrap();
397    }
398
399    #[test]
400    fn endpoint() {
401        let endpoint = ExpectedUrl::builder()
402            .endpoint("projects/simple%2Fproject/pipelines")
403            .build()
404            .unwrap();
405        let client = SingleTestClient::new_raw(endpoint, "");
406
407        let endpoint = Pipelines::builder()
408            .project("simple/project")
409            .build()
410            .unwrap();
411        api::ignore(endpoint).query(&client).unwrap();
412    }
413
414    #[test]
415    fn endpoint_scope() {
416        let endpoint = ExpectedUrl::builder()
417            .endpoint("projects/1/pipelines")
418            .add_query_params(&[("scope", "finished")])
419            .build()
420            .unwrap();
421        let client = SingleTestClient::new_raw(endpoint, "");
422
423        let endpoint = Pipelines::builder()
424            .project(1)
425            .scope(PipelineScope::Finished)
426            .build()
427            .unwrap();
428        api::ignore(endpoint).query(&client).unwrap();
429    }
430
431    #[test]
432    fn endpoint_status() {
433        let endpoint = ExpectedUrl::builder()
434            .endpoint("projects/1/pipelines")
435            .add_query_params(&[("status", "failed")])
436            .build()
437            .unwrap();
438        let client = SingleTestClient::new_raw(endpoint, "");
439
440        let endpoint = Pipelines::builder()
441            .project(1)
442            .status(PipelineStatus::Failed)
443            .build()
444            .unwrap();
445        api::ignore(endpoint).query(&client).unwrap();
446    }
447
448    #[test]
449    fn endpoint_ref() {
450        let endpoint = ExpectedUrl::builder()
451            .endpoint("projects/1/pipelines")
452            .add_query_params(&[("ref", "master")])
453            .build()
454            .unwrap();
455        let client = SingleTestClient::new_raw(endpoint, "");
456
457        let endpoint = Pipelines::builder()
458            .project(1)
459            .ref_("master")
460            .build()
461            .unwrap();
462        api::ignore(endpoint).query(&client).unwrap();
463    }
464
465    #[test]
466    fn endpoint_sha() {
467        let endpoint = ExpectedUrl::builder()
468            .endpoint("projects/1/pipelines")
469            .add_query_params(&[("sha", "0000000000000000000000000000000000000000")])
470            .build()
471            .unwrap();
472        let client = SingleTestClient::new_raw(endpoint, "");
473
474        let endpoint = Pipelines::builder()
475            .project(1)
476            .sha("0000000000000000000000000000000000000000")
477            .build()
478            .unwrap();
479        api::ignore(endpoint).query(&client).unwrap();
480    }
481
482    #[test]
483    fn endpoint_yaml_errors() {
484        let endpoint = ExpectedUrl::builder()
485            .endpoint("projects/1/pipelines")
486            .add_query_params(&[("yaml_errors", "true")])
487            .build()
488            .unwrap();
489        let client = SingleTestClient::new_raw(endpoint, "");
490
491        let endpoint = Pipelines::builder()
492            .project(1)
493            .yaml_errors(true)
494            .build()
495            .unwrap();
496        api::ignore(endpoint).query(&client).unwrap();
497    }
498
499    #[test]
500    fn endpoint_username() {
501        let endpoint = ExpectedUrl::builder()
502            .endpoint("projects/1/pipelines")
503            .add_query_params(&[("username", "name")])
504            .build()
505            .unwrap();
506        let client = SingleTestClient::new_raw(endpoint, "");
507
508        let endpoint = Pipelines::builder()
509            .project(1)
510            .username("name")
511            .build()
512            .unwrap();
513        api::ignore(endpoint).query(&client).unwrap();
514    }
515
516    #[test]
517    fn endpoint_updated_before() {
518        let endpoint = ExpectedUrl::builder()
519            .endpoint("projects/1/pipelines")
520            .add_query_params(&[("updated_before", "2020-01-01T00:00:00Z")])
521            .build()
522            .unwrap();
523        let client = SingleTestClient::new_raw(endpoint, "");
524
525        let endpoint = Pipelines::builder()
526            .project(1)
527            .updated_before(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
528            .build()
529            .unwrap();
530        api::ignore(endpoint).query(&client).unwrap();
531    }
532
533    #[test]
534    fn endpoint_updated_after() {
535        let endpoint = ExpectedUrl::builder()
536            .endpoint("projects/1/pipelines")
537            .add_query_params(&[("updated_after", "2020-01-01T00:00:00Z")])
538            .build()
539            .unwrap();
540        let client = SingleTestClient::new_raw(endpoint, "");
541
542        let endpoint = Pipelines::builder()
543            .project(1)
544            .updated_after(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
545            .build()
546            .unwrap();
547        api::ignore(endpoint).query(&client).unwrap();
548    }
549
550    #[test]
551    fn endpoint_source() {
552        let endpoint = ExpectedUrl::builder()
553            .endpoint("projects/1/pipelines")
554            .add_query_params(&[("source", "trigger")])
555            .build()
556            .unwrap();
557        let client = SingleTestClient::new_raw(endpoint, "");
558
559        let endpoint = Pipelines::builder()
560            .project(1)
561            .source(PipelineSource::Trigger)
562            .build()
563            .unwrap();
564        api::ignore(endpoint).query(&client).unwrap();
565    }
566
567    #[test]
568    fn endpoint_order_by() {
569        let endpoint = ExpectedUrl::builder()
570            .endpoint("projects/1/pipelines")
571            .add_query_params(&[("order_by", "updated_at")])
572            .build()
573            .unwrap();
574        let client = SingleTestClient::new_raw(endpoint, "");
575
576        let endpoint = Pipelines::builder()
577            .project(1)
578            .order_by(PipelineOrderBy::UpdatedAt)
579            .build()
580            .unwrap();
581        api::ignore(endpoint).query(&client).unwrap();
582    }
583
584    #[test]
585    fn endpoint_sort() {
586        let endpoint = ExpectedUrl::builder()
587            .endpoint("projects/1/pipelines")
588            .add_query_params(&[("sort", "desc")])
589            .build()
590            .unwrap();
591        let client = SingleTestClient::new_raw(endpoint, "");
592
593        let endpoint = Pipelines::builder()
594            .project(1)
595            .sort(SortOrder::Descending)
596            .build()
597            .unwrap();
598        api::ignore(endpoint).query(&client).unwrap();
599    }
600}