gitlab.rs 45.1 KB
Newer Older
1
2
3
4
5
6
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

7
use crates::itertools::Itertools;
Ben Boeckel's avatar
Ben Boeckel committed
8
use crates::percent_encoding::{utf8_percent_encode, PercentEncode, PATH_SEGMENT_ENCODE_SET};
Ben Boeckel's avatar
Ben Boeckel committed
9
use crates::reqwest::header::HeaderValue;
Ben Boeckel's avatar
Ben Boeckel committed
10
use crates::reqwest::{Client, RequestBuilder, Url};
11
use crates::serde::de::Error as SerdeError;
12
use crates::serde::de::{DeserializeOwned, Unexpected};
13
use crates::serde::ser::Serialize;
Ben Boeckel's avatar
Ben Boeckel committed
14
use crates::serde::{Deserialize, Deserializer, Serializer};
15
use crates::serde_json;
16

17
18
use error::*;
use types::*;
19

Makoto Nakashima's avatar
Makoto Nakashima committed
20
use std::borrow::Borrow;
Ben Boeckel's avatar
Ben Boeckel committed
21
use std::fmt::{self, Debug, Display};
Ben Boeckel's avatar
Ben Boeckel committed
22

23
24
25
26
27
28
29
30
31
32
33
/// A Gitlab API token
///
/// Gitlab supports two kinds of tokens
#[derive(Debug, Clone)]
enum Token {
    /// A personal access token, obtained through Gitlab user settings
    Private(String),
    /// An OAuth2 token, obtained through the OAuth2 flow
    OAuth2(String),
}

34
impl Token {
Miha Čančula's avatar
Miha Čančula committed
35
    /// Sets the appropirate header on the request.
36
37
38
    ///
    /// Depending on the token type, this will be either the Private-Token header
    /// or the Authorization header.
Miha Čančula's avatar
Miha Čančula committed
39
    /// Returns an error if the token string cannot be parsed as a header value.
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    pub fn set_header(&self, req: RequestBuilder) -> Result<RequestBuilder> {
        Ok(match self {
            Token::Private(token) => {
                let mut token_header_value =
                    HeaderValue::from_str(&token).map_err(|_| ErrorKind::HeaderValueParse)?;
                token_header_value.set_sensitive(true);
                req.header("PRIVATE-TOKEN", token_header_value)
            },
            Token::OAuth2(token) => {
                let value = format!("Bearer {}", token);
                let mut token_header_value =
                    HeaderValue::from_str(&value).map_err(|_| ErrorKind::HeaderValueParse)?;
                token_header_value.set_sensitive(true);
                req.header("Authorization", token_header_value)
            },
        })
    }
}

Ben Boeckel's avatar
Ben Boeckel committed
59
60
61
/// A representation of the Gitlab API for a single user.
///
/// Separate users should use separate instances of this.
62
pub struct Gitlab {
63
64
    /// The client to use for API calls.
    client: Client,
Ben Boeckel's avatar
Ben Boeckel committed
65
    /// The base URL to use for API calls.
66
    base_url: Url,
Ben Boeckel's avatar
Ben Boeckel committed
67
    /// The secret token to use when communicating with Gitlab.
68
    token: Token,
69
70
}

Ben Boeckel's avatar
Ben Boeckel committed
71
impl Debug for Gitlab {
Ben Boeckel's avatar
Ben Boeckel committed
72
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
73
74
75
        f.debug_struct("Gitlab")
            .field("base_url", &self.base_url)
            .finish()
Ben Boeckel's avatar
Ben Boeckel committed
76
77
78
79
    }
}

#[derive(Debug)]
80
81
82
83
84
85
86
87
88
89
90
91
/// Optional information for commit statuses.
pub struct CommitStatusInfo<'a> {
    /// The refname of the commit being tested.
    pub refname: Option<&'a str>,
    /// The name of the status (defaults to `"default"` on the Gitlab side).
    pub name: Option<&'a str>,
    /// A URL to associate with the status.
    pub target_url: Option<&'a str>,
    /// A description of the status check.
    pub description: Option<&'a str>,
}

92
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93
94
95
96
97
98
99
100
101
/// Optional information for merge requests.
pub enum MergeRequestStateFilter {
    /// Get the opened/reopened merge requests.
    Opened,
    /// Get the closes merge requests.
    Closed,
    /// Get the merged merge requests.
    Merged,
}
102
103
104
105
106
107

enum_serialize!(MergeRequestStateFilter -> "state",
    Opened => "opened",
    Closed => "closed",
    Merged => "merged",
);
108

109
impl Gitlab {
Ben Boeckel's avatar
Ben Boeckel committed
110
111
    /// Create a new Gitlab API representation.
    ///
112
    /// The `token` should be a valid [personal access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html).
Ben Boeckel's avatar
Ben Boeckel committed
113
    /// Errors out if `token` is invalid.
114
    pub fn new<H, T>(host: H, token: T) -> Result<Self>
Ben Boeckel's avatar
Ben Boeckel committed
115
116
117
    where
        H: AsRef<str>,
        T: ToString,
118
    {
119
        Self::new_impl("https", host.as_ref(), Token::Private(token.to_string()))
120
121
122
123
124
    }

    /// Create a new non-SSL Gitlab API representation.
    ///
    /// Errors out if `token` is invalid.
125
    pub fn new_insecure<H, T>(host: H, token: T) -> Result<Self>
Ben Boeckel's avatar
Ben Boeckel committed
126
127
128
    where
        H: AsRef<str>,
        T: ToString,
129
    {
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
        Self::new_impl("http", host.as_ref(), Token::Private(token.to_string()))
    }

    /// Create a new Gitlab API representation.
    ///
    /// The `token` should be a valid [OAuth2 token](https://docs.gitlab.com/ee/api/oauth2.html).
    /// Errors out if `token` is invalid.
    pub fn with_oauth2<H, T>(host: H, token: T) -> Result<Self>
    where
        H: AsRef<str>,
        T: ToString,
    {
        Self::new_impl("https", host.as_ref(), Token::OAuth2(token.to_string()))
    }

    /// Create a new non-SSL Gitlab API representation.
    ///
    /// The `token` should be a valid [OAuth2 token](https://docs.gitlab.com/ee/api/oauth2.html).
    /// Errors out if `token` is invalid.
    pub fn with_oauth2_insecure<H, T>(host: H, token: T) -> Result<Self>
    where
        H: AsRef<str>,
        T: ToString,
    {
        Self::new_impl("http", host.as_ref(), Token::OAuth2(token.to_string()))
155
156
    }

Ben Boeckel's avatar
Ben Boeckel committed
157
    /// Internal method to create a new Gitlab client.
158
    fn new_impl(protocol: &str, host: &str, token: Token) -> Result<Self> {
Brad King's avatar
Brad King committed
159
        let base_url = Url::parse(&format!("{}://{}/api/v4/", protocol, host))
160
            .chain_err(|| ErrorKind::UrlParse)?;
Ben Boeckel's avatar
Ben Boeckel committed
161

162
        let api = Gitlab {
Ben Boeckel's avatar
Ben Boeckel committed
163
            client: Client::new(),
164
165
            base_url,
            token,
166
        };
167

168
        // Ensure the API is working.
Ben Boeckel's avatar
Ben Boeckel committed
169
        let _: UserPublic = api.current_user()?;
170

171
        Ok(api)
172
173
    }

174
175
    /// Create a new Gitlab API client builder.
    pub fn builder<H, T>(host: H, token: T) -> GitlabBuilder
Ben Boeckel's avatar
Ben Boeckel committed
176
177
178
    where
        H: ToString,
        T: ToString,
179
180
181
182
    {
        GitlabBuilder::new(host, token)
    }

Ben Boeckel's avatar
Ben Boeckel committed
183
    /// The user the API is acting as.
184
    pub fn current_user(&self) -> Result<UserPublic> {
185
        self.get_with_param("user", query_param_slice![])
186
187
    }

188
    /// Get all user accounts
189
    pub fn users<T, I, K, V>(&self, params: I) -> Result<Vec<T>>
Ben Boeckel's avatar
Ben Boeckel committed
190
191
    where
        T: UserResult,
192
193
194
195
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
Ben Boeckel's avatar
Ben Boeckel committed
196
    {
197
        self.get_paged_with_param("users", params)
198
199
200
    }

    /// Find a user by id.
201
    pub fn user<T, I, K, V>(&self, user: UserId, params: I) -> Result<T>
Ben Boeckel's avatar
Ben Boeckel committed
202
203
    where
        T: UserResult,
204
205
206
207
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
Ben Boeckel's avatar
Ben Boeckel committed
208
    {
209
        self.get_with_param(&format!("users/{}", user), params)
210
211
    }

Ben Boeckel's avatar
Ben Boeckel committed
212
    /// Find a user by username.
213
    pub fn user_by_name<T, N>(&self, name: N) -> Result<T>
Ben Boeckel's avatar
Ben Boeckel committed
214
215
216
    where
        T: UserResult,
        N: AsRef<str>,
217
218
    {
        let mut users = self.get_paged_with_param("users", &[("username", name.as_ref())])?;
Ben Boeckel's avatar
Ben Boeckel committed
219
220
        users
            .pop()
221
            .ok_or_else(|| Error::from_kind(ErrorKind::Gitlab("no such user".into())))
222
223
    }

224
    /// Get all accessible projects.
225
226
227
228
229
230
231
232
    pub fn projects<I, K, V>(&self, params: I) -> Result<Vec<Project>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param("projects", params)
233
234
235
    }

    /// Get all owned projects.
Ben Boeckel's avatar
Ben Boeckel committed
236
    pub fn owned_projects(&self) -> Result<Vec<Project>> {
237
        self.get_paged_with_param("projects", &[("owned", "true")])
238
239
240
    }

    /// Find a project by id.
241
242
243
244
245
246
247
248
    pub fn project<I, K, V>(&self, project: ProjectId, params: I) -> Result<Project>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_with_param(&format!("projects/{}", project), params)
249
250
    }

251
252
253
254
255
    /// A URL-safe name for projects.
    fn url_name(name: &str) -> PercentEncode<PATH_SEGMENT_ENCODE_SET> {
        utf8_percent_encode(name, PATH_SEGMENT_ENCODE_SET)
    }

Ben Boeckel's avatar
Ben Boeckel committed
256
    /// Find a project by name.
257
    pub fn project_by_name<N, I, K, V>(&self, name: N, params: I) -> Result<Project>
Ben Boeckel's avatar
Ben Boeckel committed
258
259
    where
        N: AsRef<str>,
260
261
262
263
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
264
    {
265
266
267
268
        self.get_with_param(
            &format!("projects/{}", Self::url_name(name.as_ref())),
            params,
        )
269
270
    }

271
272
273
274
275
276
277
278
279
280
281
    /// Get all accessible groups.
    pub fn groups<I, K, V>(&self, params: I) -> Result<Vec<Group>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param("groups", params)
    }

282
    /// Get a project's hooks.
283
284
285
286
287
288
289
290
    pub fn hooks<I, K, V>(&self, project: ProjectId, params: I) -> Result<Vec<ProjectHook>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(&format!("projects/{}/hooks", project), params)
291
    }
292

293
    /// Get a project hook.
294
295
296
297
298
299
300
301
    pub fn hook<I, K, V>(&self, project: ProjectId, hook: HookId, params: I) -> Result<ProjectHook>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_with_param(&format!("projects/{}/hooks/{}", project, hook), params)
302
303
    }

Ben Boeckel's avatar
Ben Boeckel committed
304
    /// Convert a boolean parameter into an HTTP request value.
Ben Boeckel's avatar
Ben Boeckel committed
305
306
307
308
309
310
311
312
    fn bool_param_value(value: bool) -> &'static str {
        if value {
            "true"
        } else {
            "false"
        }
    }

Ben Boeckel's avatar
Ben Boeckel committed
313
    /// HTTP parameters required to register to a project.
Makoto Nakashima's avatar
Makoto Nakashima committed
314
    fn event_flags(events: WebhookEvents) -> Vec<(&'static str, &'static str)> {
Ben Boeckel's avatar
Ben Boeckel committed
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
        vec![
            ("job_events", Self::bool_param_value(events.job())),
            ("issues_events", Self::bool_param_value(events.issues())),
            (
                "confidential_issues_events",
                Self::bool_param_value(events.confidential_issues()),
            ),
            (
                "merge_requests_events",
                Self::bool_param_value(events.merge_requests()),
            ),
            ("note_events", Self::bool_param_value(events.note())),
            ("pipeline_events", Self::bool_param_value(events.pipeline())),
            ("push_events", Self::bool_param_value(events.push())),
            (
                "wiki_page_events",
                Self::bool_param_value(events.wiki_page()),
            ),
        ]
Ben Boeckel's avatar
Ben Boeckel committed
334
335
336
    }

    /// Add a project hook.
Ben Boeckel's avatar
Ben Boeckel committed
337
338
339
340
341
342
343
344
    pub fn add_hook<U>(
        &self,
        project: ProjectId,
        url: U,
        events: WebhookEvents,
    ) -> Result<ProjectHook>
    where
        U: AsRef<str>,
345
    {
Makoto Nakashima's avatar
Makoto Nakashima committed
346
        let mut flags = Self::event_flags(events);
347
        flags.push(("url", url.as_ref()));
Ben Boeckel's avatar
Ben Boeckel committed
348

349
        self.post_with_param(&format!("projects/{}/hooks", project), &flags)
Ben Boeckel's avatar
Ben Boeckel committed
350
351
    }

352
    /// Get the team members of a group.
353
354
355
356
357
358
359
360
    pub fn group_members<I, K, V>(&self, group: GroupId, params: I) -> Result<Vec<Member>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(&format!("groups/{}/members", group), params)
361
362
363
    }

    /// Get a team member of a group.
364
365
366
367
368
369
370
371
    pub fn group_member<I, K, V>(&self, group: GroupId, user: UserId, params: I) -> Result<Member>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_with_param(&format!("groups/{}/members/{}", group, user), params)
372
373
    }

374
    /// Get the team members of a project.
375
376
377
378
379
380
381
382
    pub fn project_members<I, K, V>(&self, project: ProjectId, params: I) -> Result<Vec<Member>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(&format!("projects/{}/members", project), params)
383
384
385
    }

    /// Get a team member of a project.
386
387
388
389
390
391
392
393
394
395
396
397
398
    pub fn project_member<I, K, V>(
        &self,
        project: ProjectId,
        user: UserId,
        params: I,
    ) -> Result<Member>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_with_param(&format!("projects/{}/members/{}", project, user), params)
399
400
    }

401
    /// Add a user to a project.
Ben Boeckel's avatar
Ben Boeckel committed
402
403
404
405
406
407
    pub fn add_user_to_project(
        &self,
        project: ProjectId,
        user: UserId,
        access: AccessLevel,
    ) -> Result<Member> {
408
409
410
        let user_str = format!("{}", user);
        let access_str = format!("{}", access);

Ben Boeckel's avatar
Ben Boeckel committed
411
412
413
414
        self.post_with_param(
            &format!("projects/{}/members", project),
            &[("user", &user_str), ("access", &access_str)],
        )
415
416
    }

417
    /// Add a user to a project.
Ben Boeckel's avatar
Ben Boeckel committed
418
419
420
421
422
423
424
425
    pub fn add_user_to_project_by_name<P>(
        &self,
        project: P,
        user: UserId,
        access: AccessLevel,
    ) -> Result<Member>
    where
        P: AsRef<str>,
426
427
428
429
    {
        let user_str = format!("{}", user);
        let access_str = format!("{}", access);

Ben Boeckel's avatar
Ben Boeckel committed
430
431
        self.post_with_param(
            &format!("projects/{}/members", Self::url_name(project.as_ref())),
432
            &[("user_id", &user_str), ("access_level", &access_str)],
Ben Boeckel's avatar
Ben Boeckel committed
433
        )
434
435
    }

436
    /// Get branches for a project.
437
438
439
440
441
442
443
444
    pub fn branches<I, K, V>(&self, project: ProjectId, params: I) -> Result<Vec<RepoBranch>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(&format!("projects/{}/branches", project), params)
445
446
447
    }

    /// Get a branch.
448
    pub fn branch<B, I, K, V>(&self, project: ProjectId, branch: B, params: I) -> Result<RepoBranch>
Ben Boeckel's avatar
Ben Boeckel committed
449
450
    where
        B: AsRef<str>,
451
452
453
454
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
455
    {
456
457
458
459
460
461
462
463
        self.get_with_param(
            &format!(
                "projects/{}/repository/branches/{}",
                project,
                Self::url_name(branch.as_ref()),
            ),
            params,
        )
464
465
466
    }

    /// Get a commit.
467
    pub fn commit<C>(&self, project: ProjectId, commit: C) -> Result<RepoCommitDetail>
Ben Boeckel's avatar
Ben Boeckel committed
468
469
    where
        C: AsRef<str>,
470
    {
Brad King's avatar
Brad King committed
471
        self.get_with_param(
Ben Boeckel's avatar
Ben Boeckel committed
472
473
474
475
476
477
478
            &format!(
                "projects/{}/repository/commits/{}",
                project,
                commit.as_ref(),
            ),
            &[("stats", "true")],
        )
479
480
481
    }

    /// Get comments on a commit.
482
483
484
485
486
487
    pub fn commit_comments<C, I, K, V>(
        &self,
        project: ProjectId,
        commit: C,
        params: I,
    ) -> Result<Vec<CommitNote>>
Ben Boeckel's avatar
Ben Boeckel committed
488
489
    where
        C: AsRef<str>,
490
491
492
493
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
494
    {
495
496
497
498
499
500
501
502
        self.get_paged_with_param(
            &format!(
                "projects/{}/repository/commits/{}/comments",
                project,
                commit.as_ref(),
            ),
            params,
        )
503
504
505
    }

    /// Get comments on a commit.
Ben Boeckel's avatar
Ben Boeckel committed
506
507
508
509
510
511
512
513
514
    pub fn create_commit_comment<C, B>(
        &self,
        project: ProjectId,
        commit: C,
        body: B,
    ) -> Result<CommitNote>
    where
        C: AsRef<str>,
        B: AsRef<str>,
515
    {
Ben Boeckel's avatar
Ben Boeckel committed
516
517
518
519
520
521
522
523
        self.post_with_param(
            &format!(
                "projects/{}/repository/commits/{}/comment",
                project,
                commit.as_ref(),
            ),
            &[("note", body.as_ref())],
        )
524
525
    }

526
    /// Get comments on a commit.
Ben Boeckel's avatar
Ben Boeckel committed
527
528
529
530
531
532
533
534
535
536
    pub fn create_commit_comment_by_name<P, C, B>(
        &self,
        project: P,
        commit: C,
        body: B,
    ) -> Result<CommitNote>
    where
        P: AsRef<str>,
        C: AsRef<str>,
        B: AsRef<str>,
537
    {
Ben Boeckel's avatar
Ben Boeckel committed
538
539
540
541
542
543
544
545
        self.post_with_param(
            &format!(
                "projects/{}/repository/commits/{}/comment",
                Self::url_name(project.as_ref()),
                commit.as_ref(),
            ),
            &[("note", body.as_ref())],
        )
546
547
    }

548
    /// Get comments on a commit.
Ben Boeckel's avatar
Ben Boeckel committed
549
550
551
552
553
554
555
556
    pub fn create_commit_line_comment(
        &self,
        project: ProjectId,
        commit: &str,
        body: &str,
        path: &str,
        line: u64,
    ) -> Result<CommitNote> {
557
        let line_str = format!("{}", line);
Ben Boeckel's avatar
Ben Boeckel committed
558
        let line_type = LineType::New;
559

Ben Boeckel's avatar
Ben Boeckel committed
560
561
562
563
564
565
566
567
568
        self.post_with_param(
            &format!("projects/{}/repository/commits/{}/comment", project, commit),
            &[
                ("note", body),
                ("path", path),
                ("line", &line_str),
                ("line_type", line_type.as_str()),
            ],
        )
569
570
    }

571
    /// Get the latest statuses of a commit.
572
    pub fn commit_latest_statuses<C, I, K, V>(
Ben Boeckel's avatar
Ben Boeckel committed
573
574
575
        &self,
        project: ProjectId,
        commit: C,
576
        params: I,
Ben Boeckel's avatar
Ben Boeckel committed
577
578
579
    ) -> Result<Vec<CommitStatus>>
    where
        C: AsRef<str>,
580
581
582
583
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
584
    {
585
586
587
588
589
590
591
592
        self.get_paged_with_param(
            &format!(
                "projects/{}/repository/commits/{}/statuses",
                project,
                commit.as_ref(),
            ),
            params,
        )
593
594
    }

595
    /// Get the latest statuses of a commit.
596
    pub fn commit_latest_statuses_by_name<P, C, I, K, V>(
Ben Boeckel's avatar
Ben Boeckel committed
597
598
599
        &self,
        project: P,
        commit: C,
600
        params: I,
Ben Boeckel's avatar
Ben Boeckel committed
601
602
603
604
    ) -> Result<Vec<CommitStatus>>
    where
        P: AsRef<str>,
        C: AsRef<str>,
605
606
607
608
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
609
    {
610
611
612
613
614
615
616
617
        self.get_paged_with_param(
            &format!(
                "projects/{}/repository/commits/{}/statuses",
                Self::url_name(project.as_ref()),
                commit.as_ref(),
            ),
            params,
        )
618
619
    }

620
    /// Get the all statuses of a commit.
Ben Boeckel's avatar
Ben Boeckel committed
621
622
623
    pub fn commit_all_statuses<C>(&self, project: ProjectId, commit: C) -> Result<Vec<CommitStatus>>
    where
        C: AsRef<str>,
624
    {
Ben Boeckel's avatar
Ben Boeckel committed
625
626
627
628
629
630
631
632
        self.get_paged_with_param(
            &format!(
                "projects/{}/repository/commits/{}/statuses",
                project,
                commit.as_ref(),
            ),
            &[("all", "true")],
        )
633
634
    }

635
    /// Get the latest builds of a commit.
636
637
638
639
640
641
    pub fn commit_latest_builds<C, I, K, V>(
        &self,
        project: ProjectId,
        commit: C,
        params: I,
    ) -> Result<Vec<Job>>
Ben Boeckel's avatar
Ben Boeckel committed
642
643
    where
        C: AsRef<str>,
644
645
646
647
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
648
    {
649
650
651
652
653
654
655
656
        self.get_paged_with_param(
            &format!(
                "projects/{}/repository/commits/{}/builds",
                project,
                commit.as_ref(),
            ),
            params,
        )
Makoto Nakashima's avatar
Makoto Nakashima committed
657
658
    }

659
    /// Get the all builds of a commit.
660
    pub fn commit_all_builds<C>(&self, project: ProjectId, commit: C) -> Result<Vec<Job>>
Ben Boeckel's avatar
Ben Boeckel committed
661
662
    where
        C: AsRef<str>,
663
    {
Ben Boeckel's avatar
Ben Boeckel committed
664
665
666
667
668
669
670
671
        self.get_paged_with_param(
            &format!(
                "projects/{}/repository/commits/{}/builds",
                project,
                commit.as_ref(),
            ),
            &[("all", "true")],
        )
Makoto Nakashima's avatar
Makoto Nakashima committed
672
673
    }

Ben Boeckel's avatar
Ben Boeckel committed
674
    /// Create a status message for a commit.
Ben Boeckel's avatar
Ben Boeckel committed
675
676
677
678
679
680
681
682
683
    pub fn create_commit_status<S>(
        &self,
        project: ProjectId,
        sha: S,
        state: StatusState,
        info: &CommitStatusInfo,
    ) -> Result<CommitStatus>
    where
        S: AsRef<str>,
684
    {
685
        let path = format!("projects/{}/statuses/{}", project, sha.as_ref());
686

Makoto Nakashima's avatar
Makoto Nakashima committed
687
        let mut params = vec![("state", state.as_str())];
688

689
690
691
692
693
694
695
696
697
698
699
700
        if let Some(v) = info.refname {
            params.push(("ref", v))
        }
        if let Some(v) = info.name {
            params.push(("name", v))
        }
        if let Some(v) = info.target_url {
            params.push(("target_url", v))
        }
        if let Some(v) = info.description {
            params.push(("description", v))
        }
701

702
        self.post_with_param(&path, &params)
703
704
    }

705
    /// Create a status message for a commit.
Ben Boeckel's avatar
Ben Boeckel committed
706
707
708
709
710
711
712
713
714
715
    pub fn create_commit_status_by_name<P, S>(
        &self,
        project: P,
        sha: S,
        state: StatusState,
        info: &CommitStatusInfo,
    ) -> Result<CommitStatus>
    where
        P: AsRef<str>,
        S: AsRef<str>,
716
    {
Ben Boeckel's avatar
Ben Boeckel committed
717
718
719
720
721
        let path = &format!(
            "projects/{}/statuses/{}",
            Self::url_name(project.as_ref()),
            sha.as_ref(),
        );
722
723
724

        let mut params = vec![("state", state.as_str())];

725
726
727
728
729
730
731
732
733
734
735
736
        if let Some(v) = info.refname {
            params.push(("ref", v))
        }
        if let Some(v) = info.name {
            params.push(("name", v))
        }
        if let Some(v) = info.target_url {
            params.push(("target_url", v))
        }
        if let Some(v) = info.description {
            params.push(("description", v))
        }
737

738
        self.post_with_param(&path, &params)
739
740
    }

741
742
743
744
    /// Get the labels for a project.
    pub fn labels(&self, project: ProjectId) -> Result<Vec<Label>> {
        self.get_paged(&format!("projects/{}/labels", project))
    }
745
746
747

    /// Get the labels with open/closed/merge requests count
    pub fn labels_with_counts(&self, project: ProjectId) -> Result<Vec<Label>> {
Ben Boeckel's avatar
Ben Boeckel committed
748
749
        self.get_paged_with_param(
            &format!("projects/{}/labels", project),
750
            &[("with_counts", "true")],
Ben Boeckel's avatar
Ben Boeckel committed
751
        )
752
    }
753
754
755
756
757
758

    /// Get label by ID.
    pub fn label(&self, project: ProjectId, label: LabelId) -> Result<Label> {
        self.get(&format!("projects/{}/labels/{}", project, label))
    }

Ben Boeckel's avatar
Ben Boeckel committed
759
    /// Get the issues for a project.
760
761
762
763
764
765
766
767
    pub fn issues<I, K, V>(&self, project: ProjectId, params: I) -> Result<Vec<Issue>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(&format!("projects/{}/issues", project), params)
Ben Boeckel's avatar
Ben Boeckel committed
768
769
770
    }

    /// Get issues.
771
772
773
774
775
776
777
778
779
780
781
782
783
    pub fn issue<I, K, V>(
        &self,
        project: ProjectId,
        issue: IssueInternalId,
        params: I,
    ) -> Result<Issue>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_with_param(&format!("projects/{}/issues/{}", project, issue), params)
Ben Boeckel's avatar
Ben Boeckel committed
784
785
786
    }

    /// Get the notes from a issue.
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
    pub fn issue_notes<I, K, V>(
        &self,
        project: ProjectId,
        issue: IssueInternalId,
        params: I,
    ) -> Result<Vec<Note>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(
            &format!("projects/{}/issues/{}/notes", project, issue),
            params,
        )
Ben Boeckel's avatar
Ben Boeckel committed
803
804
    }

805
    /// Get the notes from a issue.
806
807
808
809
810
811
    pub fn issue_notes_by_name<P, I, K, V>(
        &self,
        project: P,
        issue: IssueInternalId,
        params: I,
    ) -> Result<Vec<Note>>
Ben Boeckel's avatar
Ben Boeckel committed
812
813
    where
        P: AsRef<str>,
814
815
816
817
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
818
    {
819
820
821
822
823
824
825
826
        self.get_paged_with_param(
            &format!(
                "projects/{}/issues/{}/notes",
                Self::url_name(project.as_ref()),
                issue,
            ),
            params,
        )
827
828
    }

829
    /// Create a new label
Ben Boeckel's avatar
Ben Boeckel committed
830
    pub fn create_label(&self, project: ProjectId, label: Label) -> Result<Label> {
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
        let path = &format!("projects/{}/labels", project);

        let mut params: Vec<(&str, String)> = Vec::new();

        params.push(("name", label.name));
        params.push(("color", label.color.value()));

        if let Some(d) = label.description {
            params.push(("description", d));
        }

        if let Some(p) = label.priority {
            params.push(("priority", p.to_string()));
        }

        self.post_with_param(path, &params)
    }

VC's avatar
VC committed
849
    /// Create a new milestone
Ben Boeckel's avatar
Ben Boeckel committed
850
    pub fn create_milestone(&self, project: ProjectId, milestone: Milestone) -> Result<Milestone> {
VC's avatar
VC committed
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
        let path = &format!("projects/{}/milestones", project);

        let mut params: Vec<(&str, String)> = Vec::new();

        params.push(("title", milestone.title));

        if let Some(d) = milestone.description {
            params.push(("description", d));
        }

        if let Some(d) = milestone.due_date {
            params.push(("due_date", d.to_string()))
        }

        if let Some(s) = milestone.start_date {
            params.push(("start_date", s.to_string()))
        }

        self.post_with_param(path, &params)
    }

VC's avatar
VC committed
872
    /// Create a new issue
Ben Boeckel's avatar
Ben Boeckel committed
873
    pub fn create_issue(&self, project: ProjectId, issue: Issue) -> Result<Issue> {
VC's avatar
VC committed
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
        let path = &format!("projects/{}/issues", project);

        let mut params: Vec<(&str, String)> = Vec::new();

        if issue.iid.value() != 0 {
            params.push(("iid", issue.iid.value().to_string()));
        }

        params.push(("title", issue.title));

        if let Some(d) = issue.description {
            params.push(("description", d));
        }

        params.push(("confidential", issue.confidential.to_string()));

        if let Some(v) = issue.assignees {
Ben Boeckel's avatar
Ben Boeckel committed
891
892
893
894
            params.extend(
                v.into_iter()
                    .map(|x| ("assignee_ids[]", x.id.value().to_string())),
            );
VC's avatar
VC committed
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
        }

        if let Some(m) = issue.milestone {
            params.push(("milestone_id", m.id.value().to_string()))
        }

        if !issue.labels.is_empty() {
            params.push(("labels", issue.labels.join(",")));
        }

        params.push(("created_at", issue.created_at.to_string()));

        if let Some(d) = issue.due_date {
            params.push(("due_date", d.to_string()))
        }

        self.post_with_param(path, &params)
    }

Andrew Chin's avatar
Andrew Chin committed
914
915
916
917
918
    /// Get the resource label events from an issue.
    pub fn issue_label_events(
        &self,
        project: ProjectId,
        issue: IssueInternalId,
Ben Boeckel's avatar
Ben Boeckel committed
919
920
921
922
923
    ) -> Result<Vec<ResourceLabelEvent>> {
        self.get_paged(&format!(
            "projects/{}/issues/{}/resource_label_events",
            project, issue,
        ))
Andrew Chin's avatar
Andrew Chin committed
924
925
    }

Ben Boeckel's avatar
Ben Boeckel committed
926
    /// Create a note on a issue.
Ben Boeckel's avatar
Ben Boeckel committed
927
928
929
930
931
932
933
934
    pub fn create_issue_note<C>(
        &self,
        project: ProjectId,
        issue: IssueInternalId,
        content: C,
    ) -> Result<Note>
    where
        C: AsRef<str>,
935
    {
936
        let path = format!("projects/{}/issues/{}/notes", project, issue);
Ben Boeckel's avatar
Ben Boeckel committed
937

938
        self.post_with_param(&path, &[("body", content.as_ref())])
Ben Boeckel's avatar
Ben Boeckel committed
939
940
    }

941
    /// Create a note on a issue.
Ben Boeckel's avatar
Ben Boeckel committed
942
943
944
945
946
947
948
949
950
    pub fn create_issue_note_by_name<P, C>(
        &self,
        project: P,
        issue: IssueInternalId,
        content: C,
    ) -> Result<Note>
    where
        P: AsRef<str>,
        C: AsRef<str>,
951
    {
Ben Boeckel's avatar
Ben Boeckel committed
952
953
954
955
956
        let path = &format!(
            "projects/{}/issues/{}/notes",
            Self::url_name(project.as_ref()),
            issue,
        );
957

958
        self.post_with_param(&path, &[("body", content.as_ref())])
959
960
    }

961
    /// Get the merge requests for a project.
962
963
964
965
966
967
968
969
970
971
972
973
    pub fn merge_requests<I, K, V>(
        &self,
        project: ProjectId,
        params: I,
    ) -> Result<Vec<MergeRequest>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(&format!("projects/{}/merge_requests", project), params)
974
975
    }

976
    /// Get the merge requests with a given state.
Ben Boeckel's avatar
Ben Boeckel committed
977
978
979
980
981
982
983
984
985
    pub fn merge_requests_with_state(
        &self,
        project: ProjectId,
        state: MergeRequestStateFilter,
    ) -> Result<Vec<MergeRequest>> {
        self.get_paged_with_param(
            &format!("projects/{}/merge_requests", project),
            &[("state", state.as_str())],
        )
986
987
    }

988
989
990
991
992
993
994
995
    /// Get all pipelines for a project.
    pub fn pipelines<I, K, V>(&self, project: ProjectId, params: I) -> Result<Vec<PipelineBasic>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
Ben Boeckel's avatar
Ben Boeckel committed
996
        self.get_paged_with_param(&format!("projects/{}/pipelines", project), params)
997
998
999
    }

    /// Get a single pipeline.
Ben Boeckel's avatar
Ben Boeckel committed
1000
1001
    pub fn pipeline(&self, project: ProjectId, id: PipelineId) -> Result<Pipeline> {
        self.get(&format!("projects/{}/pipelines/{}", project, id))
1002
1003
1004
    }

    /// Get variables of a pipeline.
Ben Boeckel's avatar
Ben Boeckel committed
1005
1006
1007
1008
1009
1010
    pub fn pipeline_variables(
        &self,
        project: ProjectId,
        id: PipelineId,
    ) -> Result<Vec<PipelineVariable>> {
        self.get(&format!("projects/{}/pipelines/{}/variables", project, id))
1011
1012
1013
    }

    /// Create a new pipeline.
Ben Boeckel's avatar
Ben Boeckel committed
1014
1015
1016
1017
1018
1019
    pub fn create_pipeline(
        &self,
        project: ProjectId,
        ref_: ObjectId,
        variables: &[PipelineVariable],
    ) -> Result<Pipeline> {
1020
1021
1022
1023
        use crates::serde::Serialize;
        #[derive(Debug, Serialize)]
        struct CreatePipelineParams<'a> {
            ref_: ObjectId,
Ben Boeckel's avatar
Ben Boeckel committed
1024
            variables: &'a [PipelineVariable],
1025
1026
1027
        }

        self.post_with_param(
Ben Boeckel's avatar
Ben Boeckel committed
1028
1029
            &format!("projects/{}/pipeline", project),
            CreatePipelineParams {
1030
1031
                ref_,
                variables,
1032
1033
1034
1035
1036
            },
        )
    }

    /// Retry jobs in a pipeline.
Ben Boeckel's avatar
Ben Boeckel committed
1037
1038
    pub fn retry_pipeline(&self, project: ProjectId, id: PipelineId) -> Result<Pipeline> {
        self.post(&format!("projects/{}/pipelines/{}/retry", project, id))
1039
1040
1041
    }

    /// Cancel a pipeline.
Ben Boeckel's avatar
Ben Boeckel committed
1042
1043
    pub fn cancel_pipeline(&self, project: ProjectId, id: PipelineId) -> Result<Pipeline> {
        self.post(&format!("projects/{}/pipelines/{}/cancel", project, id))
1044
1045
1046
1047
1048
1049
    }

    #[allow(unused)]
    /// Delete a pipeline.
    ///
    /// NOTE Not implemented.
Ben Boeckel's avatar
Ben Boeckel committed
1050
    fn delete_pipeline(&self, project: ProjectId, id: PipelineId) -> Result<Pipeline> {
1051
1052
1053
        unimplemented!();
    }

1054
    /// Get merge requests.
1055
    pub fn merge_request<I, K, V>(
Ben Boeckel's avatar
Ben Boeckel committed
1056
1057
1058
        &self,
        project: ProjectId,
        merge_request: MergeRequestInternalId,
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
        params: I,
    ) -> Result<MergeRequest>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_with_param(
            &format!("projects/{}/merge_requests/{}", project, merge_request),
            params,
        )
1071
1072
    }

1073
    /// Get the issues that will be closed when a merge request is merged.
1074
    pub fn merge_request_closes_issues<I, K, V>(
Ben Boeckel's avatar
Ben Boeckel committed
1075
1076
1077
        &self,
        project: ProjectId,
        merge_request: MergeRequestInternalId,
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
        params: I,
    ) -> Result<Vec<IssueReference>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(
            &format!(
                "projects/{}/merge_requests/{}/closes_issues",
                project, merge_request,
            ),
            params,
        )
1093
1094
    }

1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
    /// Get the discussions from a merge request.
    pub fn merge_request_discussions<I, K, V>(
        &self,
        project: ProjectId,
        merge_request: MergeRequestInternalId,
        params: I,
    ) -> Result<Vec<Discussion>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(
            &format!(
                "projects/{}/merge_requests/{}/discussions",
                project, merge_request,
            ),
            params,
        )
    }

1117
    /// Get the notes from a merge request.
1118
    pub fn merge_request_notes<I, K, V>(
Ben Boeckel's avatar
Ben Boeckel committed
1119
1120
1121
        &self,
        project: ProjectId,
        merge_request: MergeRequestInternalId,
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
        params: I,
    ) -> Result<Vec<Note>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(
            &format!(
                "projects/{}/merge_requests/{}/notes",
                project, merge_request,
            ),
            params,
        )
1137
1138
    }

1139
    /// Get the notes from a merge request.
1140
    pub fn merge_request_notes_by_name<P, I, K, V>(
Ben Boeckel's avatar
Ben Boeckel committed
1141
1142
1143
        &self,
        project: P,
        merge_request: MergeRequestInternalId,
1144
        params: I,
Ben Boeckel's avatar
Ben Boeckel committed
1145
1146
1147
    ) -> Result<Vec<Note>>
    where
        P: AsRef<str>,
1148
1149
1150
1151
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
1152
    {
1153
1154
1155
1156
1157
1158
1159
1160
        self.get_paged_with_param(
            &format!(
                "projects/{}/merge_requests/{}/notes",
                Self::url_name(project.as_ref()),
                merge_request,
            ),
            params,
        )
1161
1162
    }

1163
    /// Award a merge request note with an award.
Ben Boeckel's avatar
Ben Boeckel committed
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
    pub fn award_merge_request_note(
        &self,
        project: ProjectId,
        merge_request: MergeRequestInternalId,
        note: NoteId,
        award: &str,
    ) -> Result<AwardEmoji> {
        let path = &format!(
            "projects/{}/merge_requests/{}/notes/{}/award_emoji",
            project, merge_request, note,
        );
1175
        self.post_with_param(path, &[("name", award)])
1176
1177
    }

1178
    /// Award a merge request note with an award.
Ben Boeckel's avatar
Ben Boeckel committed
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
    pub fn award_merge_request_note_by_name<P>(
        &self,
        project: P,
        merge_request: MergeRequestInternalId,
        note: NoteId,
        award: &str,
    ) -> Result<AwardEmoji>
    where
        P: AsRef<str>,
    {
        let path = &format!(
            "projects/{}/merge_requests/{}/notes/{}/award_emoji",
            Self::url_name(project.as_ref()),
            merge_request,
            note,
        );
1195
1196
1197
        self.post_with_param(path, &[("name", award)])
    }

1198
    /// Get the awards for a merge request.
1199
    pub fn merge_request_awards<I, K, V>(
Ben Boeckel's avatar
Ben Boeckel committed
1200
1201
1202
        &self,
        project: ProjectId,
        merge_request: MergeRequestInternalId,
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
        params: I,
    ) -> Result<Vec<AwardEmoji>>
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        self.get_paged_with_param(
            &format!(
                "projects/{}/merge_requests/{}/award_emoji",
                project, merge_request,
            ),
            params,
        )
1218
1219
    }

1220
    /// Get the awards for a merge request.
1221
    pub fn merge_request_awards_by_name<P, I, K, V>(
Ben Boeckel's avatar
Ben Boeckel committed
1222
1223
1224
        &self,
        project: P,
        merge_request: MergeRequestInternalId,
1225
        params: I,
Ben Boeckel's avatar
Ben Boeckel committed
1226
1227
1228
    ) -> Result<Vec<AwardEmoji>>
    where
        P: AsRef<str>,
1229
1230
1231
1232
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
1233
    {
1234
1235
1236
1237
1238
1239
1240
1241
        self.get_paged_with_param(
            &format!(
                "projects/{}/merge_requests/{}/award_emoji",
                Self::url_name(project.as_ref()),
                merge_request,
            ),
            params,
        )
1242