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    /// Filter pipelines by the name of the person who triggered the pipeline.
230    #[builder(setter(into), default)]
231    name: Option<Cow<'a, str>>,
232
233    /// Order results by a given key.
234    #[builder(default)]
235    order_by: Option<PipelineOrderBy>,
236    /// Sort order for resulting pipelines.
237    #[builder(default)]
238    sort: Option<SortOrder>,
239
240    /// Filter pipelines by the last updated date before this time.
241    #[builder(default)]
242    updated_before: Option<DateTime<Utc>>,
243    /// Filter pipelines by the last updated date after this time.
244    #[builder(default)]
245    updated_after: Option<DateTime<Utc>>,
246    /// How the pipeline was triggered.
247    #[builder(default)]
248    source: Option<PipelineSource>,
249}
250
251impl<'a> Pipelines<'a> {
252    /// Create a builder for the endpoint.
253    pub fn builder() -> PipelinesBuilder<'a> {
254        PipelinesBuilder::default()
255    }
256}
257
258impl Endpoint for Pipelines<'_> {
259    fn method(&self) -> Method {
260        Method::GET
261    }
262
263    fn endpoint(&self) -> Cow<'static, str> {
264        format!("projects/{}/pipelines", self.project).into()
265    }
266
267    fn parameters(&self) -> QueryParams {
268        let mut params = QueryParams::default();
269
270        params
271            .push_opt("scope", self.scope)
272            .push_opt("status", self.status)
273            .push_opt("ref", self.ref_.as_ref())
274            .push_opt("sha", self.sha.as_ref())
275            .push_opt("yaml_errors", self.yaml_errors)
276            .push_opt("username", self.username.as_ref())
277            .push_opt("name", self.name.as_ref())
278            .push_opt("updated_after", self.updated_after)
279            .push_opt("updated_before", self.updated_before)
280            .push_opt("source", self.source)
281            .push_opt("order_by", self.order_by)
282            .push_opt("sort", self.sort);
283
284        params
285    }
286}
287
288impl Pageable for Pipelines<'_> {}
289
290#[cfg(test)]
291mod tests {
292    use chrono::{TimeZone, Utc};
293
294    use crate::api::common::SortOrder;
295    use crate::api::projects::pipelines::{
296        PipelineOrderBy, PipelineScope, PipelineSource, PipelineStatus, Pipelines,
297        PipelinesBuilderError,
298    };
299    use crate::api::{self, Query};
300    use crate::test::client::{ExpectedUrl, SingleTestClient};
301
302    #[test]
303    fn pipeline_scope_as_str() {
304        let items = &[
305            (PipelineScope::Running, "running"),
306            (PipelineScope::Pending, "pending"),
307            (PipelineScope::Finished, "finished"),
308            (PipelineScope::Branches, "branches"),
309            (PipelineScope::Tags, "tags"),
310        ];
311
312        for (i, s) in items {
313            assert_eq!(i.as_str(), *s);
314        }
315    }
316
317    #[test]
318    fn pipeline_status_as_str() {
319        let items = &[
320            (PipelineStatus::Running, "running"),
321            (PipelineStatus::Pending, "pending"),
322            (PipelineStatus::Success, "success"),
323            (PipelineStatus::Failed, "failed"),
324            (PipelineStatus::Canceled, "canceled"),
325            (PipelineStatus::Skipped, "skipped"),
326            (PipelineStatus::Created, "created"),
327            (PipelineStatus::Manual, "manual"),
328            (PipelineStatus::Scheduled, "scheduled"),
329            (PipelineStatus::Preparing, "preparing"),
330            (PipelineStatus::WaitingForResource, "waiting_for_resource"),
331        ];
332
333        for (i, s) in items {
334            assert_eq!(i.as_str(), *s);
335        }
336    }
337
338    #[test]
339    fn order_by_default() {
340        assert_eq!(PipelineOrderBy::default(), PipelineOrderBy::Id);
341    }
342
343    #[test]
344    fn order_by_as_str() {
345        let items = &[
346            (PipelineOrderBy::Id, "id"),
347            (PipelineOrderBy::Status, "status"),
348            (PipelineOrderBy::Ref, "ref"),
349            (PipelineOrderBy::UpdatedAt, "updated_at"),
350            (PipelineOrderBy::UserId, "user_id"),
351        ];
352
353        for (i, s) in items {
354            assert_eq!(i.as_str(), *s);
355        }
356    }
357
358    #[test]
359    fn pipeline_source_as_str() {
360        let items = &[
361            (PipelineSource::Push, "push"),
362            (PipelineSource::Web, "web"),
363            (PipelineSource::Trigger, "trigger"),
364            (PipelineSource::Schedule, "schedule"),
365            (PipelineSource::Api, "api"),
366            (PipelineSource::External, "external"),
367            (PipelineSource::Pipeline, "pipeline"),
368            (PipelineSource::Chat, "chat"),
369            (PipelineSource::WebIde, "web_ide"),
370            (PipelineSource::MergeRequestEvent, "merge_request_event"),
371            (
372                PipelineSource::ExternalPullRequestEvent,
373                "external_pull_request_event",
374            ),
375            (PipelineSource::ParentPipeline, "parent_pipeline"),
376            (PipelineSource::OnDemandDastScan, "ondemand_dast_scan"),
377            (
378                PipelineSource::OnDemandDastValidation,
379                "ondemand_dast_validation",
380            ),
381            (
382                PipelineSource::SecurityOrchestrationPolicy,
383                "security_orchestration_policy",
384            ),
385        ];
386
387        for (i, s) in items {
388            assert_eq!(i.as_str(), *s);
389        }
390    }
391
392    #[test]
393    fn project_is_needed() {
394        let err = Pipelines::builder().build().unwrap_err();
395        crate::test::assert_missing_field!(err, PipelinesBuilderError, "project");
396    }
397
398    #[test]
399    fn project_is_sufficient() {
400        Pipelines::builder().project(1).build().unwrap();
401    }
402
403    #[test]
404    fn endpoint() {
405        let endpoint = ExpectedUrl::builder()
406            .endpoint("projects/simple%2Fproject/pipelines")
407            .build()
408            .unwrap();
409        let client = SingleTestClient::new_raw(endpoint, "");
410
411        let endpoint = Pipelines::builder()
412            .project("simple/project")
413            .build()
414            .unwrap();
415        api::ignore(endpoint).query(&client).unwrap();
416    }
417
418    #[test]
419    fn endpoint_scope() {
420        let endpoint = ExpectedUrl::builder()
421            .endpoint("projects/1/pipelines")
422            .add_query_params(&[("scope", "finished")])
423            .build()
424            .unwrap();
425        let client = SingleTestClient::new_raw(endpoint, "");
426
427        let endpoint = Pipelines::builder()
428            .project(1)
429            .scope(PipelineScope::Finished)
430            .build()
431            .unwrap();
432        api::ignore(endpoint).query(&client).unwrap();
433    }
434
435    #[test]
436    fn endpoint_status() {
437        let endpoint = ExpectedUrl::builder()
438            .endpoint("projects/1/pipelines")
439            .add_query_params(&[("status", "failed")])
440            .build()
441            .unwrap();
442        let client = SingleTestClient::new_raw(endpoint, "");
443
444        let endpoint = Pipelines::builder()
445            .project(1)
446            .status(PipelineStatus::Failed)
447            .build()
448            .unwrap();
449        api::ignore(endpoint).query(&client).unwrap();
450    }
451
452    #[test]
453    fn endpoint_ref() {
454        let endpoint = ExpectedUrl::builder()
455            .endpoint("projects/1/pipelines")
456            .add_query_params(&[("ref", "master")])
457            .build()
458            .unwrap();
459        let client = SingleTestClient::new_raw(endpoint, "");
460
461        let endpoint = Pipelines::builder()
462            .project(1)
463            .ref_("master")
464            .build()
465            .unwrap();
466        api::ignore(endpoint).query(&client).unwrap();
467    }
468
469    #[test]
470    fn endpoint_sha() {
471        let endpoint = ExpectedUrl::builder()
472            .endpoint("projects/1/pipelines")
473            .add_query_params(&[("sha", "0000000000000000000000000000000000000000")])
474            .build()
475            .unwrap();
476        let client = SingleTestClient::new_raw(endpoint, "");
477
478        let endpoint = Pipelines::builder()
479            .project(1)
480            .sha("0000000000000000000000000000000000000000")
481            .build()
482            .unwrap();
483        api::ignore(endpoint).query(&client).unwrap();
484    }
485
486    #[test]
487    fn endpoint_yaml_errors() {
488        let endpoint = ExpectedUrl::builder()
489            .endpoint("projects/1/pipelines")
490            .add_query_params(&[("yaml_errors", "true")])
491            .build()
492            .unwrap();
493        let client = SingleTestClient::new_raw(endpoint, "");
494
495        let endpoint = Pipelines::builder()
496            .project(1)
497            .yaml_errors(true)
498            .build()
499            .unwrap();
500        api::ignore(endpoint).query(&client).unwrap();
501    }
502
503    #[test]
504    fn endpoint_username() {
505        let endpoint = ExpectedUrl::builder()
506            .endpoint("projects/1/pipelines")
507            .add_query_params(&[("username", "name")])
508            .build()
509            .unwrap();
510        let client = SingleTestClient::new_raw(endpoint, "");
511
512        let endpoint = Pipelines::builder()
513            .project(1)
514            .username("name")
515            .build()
516            .unwrap();
517        api::ignore(endpoint).query(&client).unwrap();
518    }
519
520    #[test]
521    fn endpoint_name() {
522        let endpoint = ExpectedUrl::builder()
523            .endpoint("projects/1/pipelines")
524            .add_query_params(&[("name", "my-pipeline-name")])
525            .build()
526            .unwrap();
527        let client = SingleTestClient::new_raw(endpoint, "");
528
529        let endpoint = Pipelines::builder()
530            .project(1)
531            .name("my-pipeline-name")
532            .build()
533            .unwrap();
534        api::ignore(endpoint).query(&client).unwrap();
535    }
536
537    #[test]
538    fn endpoint_updated_after() {
539        let endpoint = ExpectedUrl::builder()
540            .endpoint("projects/1/pipelines")
541            .add_query_params(&[("updated_after", "2020-01-01T00:00:00Z")])
542            .build()
543            .unwrap();
544        let client = SingleTestClient::new_raw(endpoint, "");
545
546        let endpoint = Pipelines::builder()
547            .project(1)
548            .updated_after(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
549            .build()
550            .unwrap();
551        api::ignore(endpoint).query(&client).unwrap();
552    }
553
554    #[test]
555    fn endpoint_updated_before() {
556        let endpoint = ExpectedUrl::builder()
557            .endpoint("projects/1/pipelines")
558            .add_query_params(&[("updated_before", "2020-01-01T00:00:00Z")])
559            .build()
560            .unwrap();
561        let client = SingleTestClient::new_raw(endpoint, "");
562
563        let endpoint = Pipelines::builder()
564            .project(1)
565            .updated_before(Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap())
566            .build()
567            .unwrap();
568        api::ignore(endpoint).query(&client).unwrap();
569    }
570
571    #[test]
572    fn endpoint_source() {
573        let endpoint = ExpectedUrl::builder()
574            .endpoint("projects/1/pipelines")
575            .add_query_params(&[("source", "trigger")])
576            .build()
577            .unwrap();
578        let client = SingleTestClient::new_raw(endpoint, "");
579
580        let endpoint = Pipelines::builder()
581            .project(1)
582            .source(PipelineSource::Trigger)
583            .build()
584            .unwrap();
585        api::ignore(endpoint).query(&client).unwrap();
586    }
587
588    #[test]
589    fn endpoint_order_by() {
590        let endpoint = ExpectedUrl::builder()
591            .endpoint("projects/1/pipelines")
592            .add_query_params(&[("order_by", "updated_at")])
593            .build()
594            .unwrap();
595        let client = SingleTestClient::new_raw(endpoint, "");
596
597        let endpoint = Pipelines::builder()
598            .project(1)
599            .order_by(PipelineOrderBy::UpdatedAt)
600            .build()
601            .unwrap();
602        api::ignore(endpoint).query(&client).unwrap();
603    }
604
605    #[test]
606    fn endpoint_sort() {
607        let endpoint = ExpectedUrl::builder()
608            .endpoint("projects/1/pipelines")
609            .add_query_params(&[("sort", "desc")])
610            .build()
611            .unwrap();
612        let client = SingleTestClient::new_raw(endpoint, "");
613
614        let endpoint = Pipelines::builder()
615            .project(1)
616            .sort(SortOrder::Descending)
617            .build()
618            .unwrap();
619        api::ignore(endpoint).query(&client).unwrap();
620    }
621}