gitlab/api/projects/hooks/
edit.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 derive_builder::Builder;
8
9use crate::api::common::NameOrId;
10use crate::api::endpoint_prelude::*;
11use crate::api::projects::hooks::BranchFilterStrategy;
12
13/// Edit an existing webhook for a project.
14#[derive(Debug, Builder, Clone)]
15#[builder(setter(strip_option))]
16pub struct EditHook<'a> {
17    /// The project to edit a webhook within.
18    #[builder(setter(into))]
19    project: NameOrId<'a>,
20    /// The ID of the hook to edit.
21    hook_id: u64,
22
23    /// The URL for the webhook to contact.
24    #[builder(setter(into), default)]
25    url: Option<Cow<'a, str>>,
26    /// The name of the hook.
27    #[builder(setter(into), default)]
28    name: Option<Cow<'a, str>>,
29    /// A description for the hook.
30    #[builder(setter(into), default)]
31    description: Option<Cow<'a, str>>,
32
33    /// Whether to send push events for this webhook or not.
34    #[builder(default)]
35    push_events: Option<bool>,
36    /// Filter which branches send push events for this webhook.
37    #[builder(setter(into), default)]
38    push_events_branch_filter: Option<Cow<'a, str>>,
39    /// The strategy to use for `push_events_branch_filter`.
40    #[builder(setter(into), default)]
41    branch_filter_strategy: Option<BranchFilterStrategy>,
42    /// Whether to send issue events for this webhook or not.
43    #[builder(default)]
44    issues_events: Option<bool>,
45    /// Whether to send confidential issue events for this webhook or not.
46    #[builder(default)]
47    confidential_issues_events: Option<bool>,
48    /// Whether to send merge request events for this webhook or not.
49    #[builder(default)]
50    merge_requests_events: Option<bool>,
51    /// Whether to send tag events for this webhook or not.
52    #[builder(default)]
53    tag_push_events: Option<bool>,
54    /// Whether to send note (comment) events for this webhook or not.
55    #[builder(default)]
56    note_events: Option<bool>,
57    /// Whether to send confidential note (comment) events for this webhook or not.
58    #[builder(default)]
59    confidential_note_events: Option<bool>,
60    /// Whether to send job events for this webhook or not.
61    #[builder(default)]
62    job_events: Option<bool>,
63    /// Whether to send pipeline events for this webhook or not.
64    #[builder(default)]
65    pipeline_events: Option<bool>,
66    /// Whether to send wiki page events for this webhook or not.
67    #[builder(default)]
68    wiki_page_events: Option<bool>,
69    /// Whether to send deployment events for this webhook or not.
70    #[builder(default)]
71    deployment_events: Option<bool>,
72    /// Whether to send release events for this webhook or not.
73    #[builder(default)]
74    releases_events: Option<bool>,
75    /// Whether to send feature flag events for this webhook or not.
76    #[builder(default)]
77    feature_flag_events: Option<bool>,
78    /// Whether to send resource access token events for this webhook or not.
79    #[builder(default)]
80    resource_access_token_events: Option<bool>,
81
82    /// The template to use for the custom webhook.
83    #[builder(setter(into), default)]
84    custom_webhook_template: Option<Cow<'a, str>>,
85    /// Custom headers to send with the webhook.
86    #[builder(setter(name = "_custom_headers"), default, private)]
87    custom_headers: Vec<(Cow<'a, str>, Cow<'a, str>)>,
88
89    /// Whether to verify SSL/TLS certificates for the webhook endpoint or not.
90    #[builder(default)]
91    enable_ssl_verification: Option<bool>,
92    /// A secret token to include in webhook deliveries.
93    ///
94    /// This may be used to ensure that the webhook is actually coming from the GitLab instance.
95    #[builder(setter(into), default)]
96    token: Option<Cow<'a, str>>,
97}
98
99impl<'a> EditHook<'a> {
100    /// Create a builder for the endpoint.
101    pub fn builder() -> EditHookBuilder<'a> {
102        EditHookBuilder::default()
103    }
104}
105
106impl<'a> EditHookBuilder<'a> {
107    /// Add a single custom header.
108    pub fn custom_header<K, V>(&mut self, key: K, value: V) -> &mut Self
109    where
110        K: Into<Cow<'a, str>>,
111        V: Into<Cow<'a, str>>,
112    {
113        self.custom_headers
114            .get_or_insert_with(Vec::new)
115            .push((key.into(), value.into()));
116        self
117    }
118
119    /// Add multiple custom headers.
120    pub fn custom_headers<I, K, V>(&mut self, iter: I) -> &mut Self
121    where
122        I: Iterator<Item = (K, V)>,
123        K: Into<Cow<'a, str>>,
124        V: Into<Cow<'a, str>>,
125    {
126        self.custom_headers
127            .get_or_insert_with(Vec::new)
128            .extend(iter.map(|(k, v)| (k.into(), v.into())));
129        self
130    }
131}
132
133impl Endpoint for EditHook<'_> {
134    fn method(&self) -> Method {
135        Method::PUT
136    }
137
138    fn endpoint(&self) -> Cow<'static, str> {
139        format!("projects/{}/hooks/{}", self.project, self.hook_id).into()
140    }
141
142    fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
143        let mut params = FormParams::default();
144
145        params
146            .push_opt("url", self.url.as_ref())
147            .push_opt("name", self.name.as_ref())
148            .push_opt("description", self.description.as_ref())
149            .push_opt("push_events", self.push_events)
150            .push_opt(
151                "push_events_branch_filter",
152                self.push_events_branch_filter.as_ref(),
153            )
154            .push_opt("branch_filter_strategy", self.branch_filter_strategy)
155            .push_opt("issues_events", self.issues_events)
156            .push_opt(
157                "confidential_issues_events",
158                self.confidential_issues_events,
159            )
160            .push_opt("merge_requests_events", self.merge_requests_events)
161            .push_opt("tag_push_events", self.tag_push_events)
162            .push_opt("note_events", self.note_events)
163            .push_opt("confidential_note_events", self.confidential_note_events)
164            .push_opt("job_events", self.job_events)
165            .push_opt("pipeline_events", self.pipeline_events)
166            .push_opt("wiki_page_events", self.wiki_page_events)
167            .push_opt("deployment_events", self.deployment_events)
168            .push_opt("releases_events", self.releases_events)
169            .push_opt("feature_flag_events", self.feature_flag_events)
170            .push_opt(
171                "resource_access_token_events",
172                self.resource_access_token_events,
173            )
174            .push_opt(
175                "custom_webhook_template",
176                self.custom_webhook_template.as_ref(),
177            )
178            .push_opt("enable_ssl_verification", self.enable_ssl_verification)
179            .push_opt("token", self.token.as_ref());
180
181        for (key, value) in &self.custom_headers {
182            params.push(format!("custom_headers[{}]", key), value);
183        }
184
185        params.into_body()
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use http::Method;
192
193    use crate::api::projects::hooks::{BranchFilterStrategy, EditHook, EditHookBuilderError};
194    use crate::api::{self, Query};
195    use crate::test::client::{ExpectedUrl, SingleTestClient};
196
197    #[test]
198    fn project_and_hook_id_are_necessary() {
199        let err = EditHook::builder().build().unwrap_err();
200        crate::test::assert_missing_field!(err, EditHookBuilderError, "project");
201    }
202
203    #[test]
204    fn project_is_necessary() {
205        let err = EditHook::builder().hook_id(1).build().unwrap_err();
206        crate::test::assert_missing_field!(err, EditHookBuilderError, "project");
207    }
208
209    #[test]
210    fn hook_id_is_necessary() {
211        let err = EditHook::builder().project("project").build().unwrap_err();
212        crate::test::assert_missing_field!(err, EditHookBuilderError, "hook_id");
213    }
214
215    #[test]
216    fn project_and_hook_id_are_sufficient() {
217        EditHook::builder()
218            .project("project")
219            .hook_id(1)
220            .build()
221            .unwrap();
222    }
223
224    #[test]
225    fn endpoint() {
226        let endpoint = ExpectedUrl::builder()
227            .method(Method::PUT)
228            .endpoint("projects/simple%2Fproject/hooks/1")
229            .content_type("application/x-www-form-urlencoded")
230            .build()
231            .unwrap();
232        let client = SingleTestClient::new_raw(endpoint, "");
233
234        let endpoint = EditHook::builder()
235            .project("simple/project")
236            .hook_id(1)
237            .build()
238            .unwrap();
239        api::ignore(endpoint).query(&client).unwrap();
240    }
241
242    #[test]
243    fn endpoint_url() {
244        let endpoint = ExpectedUrl::builder()
245            .method(Method::PUT)
246            .endpoint("projects/simple%2Fproject/hooks/1")
247            .content_type("application/x-www-form-urlencoded")
248            .body_str("url=https%3A%2F%2Ftest.invalid%2Fpath%3Fsome%3Dfoo")
249            .build()
250            .unwrap();
251        let client = SingleTestClient::new_raw(endpoint, "");
252
253        let endpoint = EditHook::builder()
254            .project("simple/project")
255            .hook_id(1)
256            .url("https://test.invalid/path?some=foo")
257            .build()
258            .unwrap();
259        api::ignore(endpoint).query(&client).unwrap();
260    }
261
262    #[test]
263    fn endpoint_name() {
264        let endpoint = ExpectedUrl::builder()
265            .method(Method::PUT)
266            .endpoint("projects/simple%2Fproject/hooks/1")
267            .content_type("application/x-www-form-urlencoded")
268            .body_str("name=my-hook")
269            .build()
270            .unwrap();
271        let client = SingleTestClient::new_raw(endpoint, "");
272
273        let endpoint = EditHook::builder()
274            .project("simple/project")
275            .hook_id(1)
276            .name("my-hook")
277            .build()
278            .unwrap();
279        api::ignore(endpoint).query(&client).unwrap();
280    }
281
282    #[test]
283    fn endpoint_description() {
284        let endpoint = ExpectedUrl::builder()
285            .method(Method::PUT)
286            .endpoint("projects/simple%2Fproject/hooks/1")
287            .content_type("application/x-www-form-urlencoded")
288            .body_str("description=A+test+hook")
289            .build()
290            .unwrap();
291        let client = SingleTestClient::new_raw(endpoint, "");
292
293        let endpoint = EditHook::builder()
294            .project("simple/project")
295            .hook_id(1)
296            .description("A test hook")
297            .build()
298            .unwrap();
299        api::ignore(endpoint).query(&client).unwrap();
300    }
301
302    #[test]
303    fn endpoint_push_events() {
304        let endpoint = ExpectedUrl::builder()
305            .method(Method::PUT)
306            .endpoint("projects/simple%2Fproject/hooks/1")
307            .content_type("application/x-www-form-urlencoded")
308            .body_str("push_events=true")
309            .build()
310            .unwrap();
311        let client = SingleTestClient::new_raw(endpoint, "");
312
313        let endpoint = EditHook::builder()
314            .project("simple/project")
315            .hook_id(1)
316            .push_events(true)
317            .build()
318            .unwrap();
319        api::ignore(endpoint).query(&client).unwrap();
320    }
321
322    #[test]
323    fn endpoint_push_events_branch_filter() {
324        let endpoint = ExpectedUrl::builder()
325            .method(Method::PUT)
326            .endpoint("projects/simple%2Fproject/hooks/1")
327            .content_type("application/x-www-form-urlencoded")
328            .body_str("push_events_branch_filter=branch%2F*%2Ffilter")
329            .build()
330            .unwrap();
331        let client = SingleTestClient::new_raw(endpoint, "");
332
333        let endpoint = EditHook::builder()
334            .project("simple/project")
335            .hook_id(1)
336            .push_events_branch_filter("branch/*/filter")
337            .build()
338            .unwrap();
339        api::ignore(endpoint).query(&client).unwrap();
340    }
341
342    #[test]
343    fn endpoint_branch_filter_strategy() {
344        let endpoint = ExpectedUrl::builder()
345            .method(Method::PUT)
346            .endpoint("projects/simple%2Fproject/hooks/1")
347            .content_type("application/x-www-form-urlencoded")
348            .body_str("branch_filter_strategy=regex")
349            .build()
350            .unwrap();
351        let client = SingleTestClient::new_raw(endpoint, "");
352
353        let endpoint = EditHook::builder()
354            .project("simple/project")
355            .hook_id(1)
356            .branch_filter_strategy(BranchFilterStrategy::Regex)
357            .build()
358            .unwrap();
359        api::ignore(endpoint).query(&client).unwrap();
360    }
361
362    #[test]
363    fn endpoint_issues_events() {
364        let endpoint = ExpectedUrl::builder()
365            .method(Method::PUT)
366            .endpoint("projects/simple%2Fproject/hooks/1")
367            .content_type("application/x-www-form-urlencoded")
368            .body_str("issues_events=false")
369            .build()
370            .unwrap();
371        let client = SingleTestClient::new_raw(endpoint, "");
372
373        let endpoint = EditHook::builder()
374            .project("simple/project")
375            .hook_id(1)
376            .issues_events(false)
377            .build()
378            .unwrap();
379        api::ignore(endpoint).query(&client).unwrap();
380    }
381
382    #[test]
383    fn endpoint_confidential_issues_events() {
384        let endpoint = ExpectedUrl::builder()
385            .method(Method::PUT)
386            .endpoint("projects/simple%2Fproject/hooks/1")
387            .content_type("application/x-www-form-urlencoded")
388            .body_str("confidential_issues_events=false")
389            .build()
390            .unwrap();
391        let client = SingleTestClient::new_raw(endpoint, "");
392
393        let endpoint = EditHook::builder()
394            .project("simple/project")
395            .hook_id(1)
396            .confidential_issues_events(false)
397            .build()
398            .unwrap();
399        api::ignore(endpoint).query(&client).unwrap();
400    }
401
402    #[test]
403    fn endpoint_merge_requests_events() {
404        let endpoint = ExpectedUrl::builder()
405            .method(Method::PUT)
406            .endpoint("projects/simple%2Fproject/hooks/1")
407            .content_type("application/x-www-form-urlencoded")
408            .body_str("merge_requests_events=false")
409            .build()
410            .unwrap();
411        let client = SingleTestClient::new_raw(endpoint, "");
412
413        let endpoint = EditHook::builder()
414            .project("simple/project")
415            .hook_id(1)
416            .merge_requests_events(false)
417            .build()
418            .unwrap();
419        api::ignore(endpoint).query(&client).unwrap();
420    }
421
422    #[test]
423    fn endpoint_tag_push_events() {
424        let endpoint = ExpectedUrl::builder()
425            .method(Method::PUT)
426            .endpoint("projects/simple%2Fproject/hooks/1")
427            .content_type("application/x-www-form-urlencoded")
428            .body_str("tag_push_events=false")
429            .build()
430            .unwrap();
431        let client = SingleTestClient::new_raw(endpoint, "");
432
433        let endpoint = EditHook::builder()
434            .project("simple/project")
435            .hook_id(1)
436            .tag_push_events(false)
437            .build()
438            .unwrap();
439        api::ignore(endpoint).query(&client).unwrap();
440    }
441
442    #[test]
443    fn endpoint_note_events() {
444        let endpoint = ExpectedUrl::builder()
445            .method(Method::PUT)
446            .endpoint("projects/simple%2Fproject/hooks/1")
447            .content_type("application/x-www-form-urlencoded")
448            .body_str("note_events=false")
449            .build()
450            .unwrap();
451        let client = SingleTestClient::new_raw(endpoint, "");
452
453        let endpoint = EditHook::builder()
454            .project("simple/project")
455            .hook_id(1)
456            .note_events(false)
457            .build()
458            .unwrap();
459        api::ignore(endpoint).query(&client).unwrap();
460    }
461
462    #[test]
463    fn endpoint_confidential_note_events() {
464        let endpoint = ExpectedUrl::builder()
465            .method(Method::PUT)
466            .endpoint("projects/simple%2Fproject/hooks/1")
467            .content_type("application/x-www-form-urlencoded")
468            .body_str("confidential_note_events=false")
469            .build()
470            .unwrap();
471        let client = SingleTestClient::new_raw(endpoint, "");
472
473        let endpoint = EditHook::builder()
474            .project("simple/project")
475            .hook_id(1)
476            .confidential_note_events(false)
477            .build()
478            .unwrap();
479        api::ignore(endpoint).query(&client).unwrap();
480    }
481
482    #[test]
483    fn endpoint_job_events() {
484        let endpoint = ExpectedUrl::builder()
485            .method(Method::PUT)
486            .endpoint("projects/simple%2Fproject/hooks/1")
487            .content_type("application/x-www-form-urlencoded")
488            .body_str("job_events=false")
489            .build()
490            .unwrap();
491        let client = SingleTestClient::new_raw(endpoint, "");
492
493        let endpoint = EditHook::builder()
494            .project("simple/project")
495            .hook_id(1)
496            .job_events(false)
497            .build()
498            .unwrap();
499        api::ignore(endpoint).query(&client).unwrap();
500    }
501
502    #[test]
503    fn endpoint_pipeline_events() {
504        let endpoint = ExpectedUrl::builder()
505            .method(Method::PUT)
506            .endpoint("projects/simple%2Fproject/hooks/1")
507            .content_type("application/x-www-form-urlencoded")
508            .body_str("pipeline_events=false")
509            .build()
510            .unwrap();
511        let client = SingleTestClient::new_raw(endpoint, "");
512
513        let endpoint = EditHook::builder()
514            .project("simple/project")
515            .hook_id(1)
516            .pipeline_events(false)
517            .build()
518            .unwrap();
519        api::ignore(endpoint).query(&client).unwrap();
520    }
521
522    #[test]
523    fn endpoint_wiki_page_events() {
524        let endpoint = ExpectedUrl::builder()
525            .method(Method::PUT)
526            .endpoint("projects/simple%2Fproject/hooks/1")
527            .content_type("application/x-www-form-urlencoded")
528            .body_str("wiki_page_events=false")
529            .build()
530            .unwrap();
531        let client = SingleTestClient::new_raw(endpoint, "");
532
533        let endpoint = EditHook::builder()
534            .project("simple/project")
535            .hook_id(1)
536            .wiki_page_events(false)
537            .build()
538            .unwrap();
539        api::ignore(endpoint).query(&client).unwrap();
540    }
541
542    #[test]
543    fn endpoint_deployment_events() {
544        let endpoint = ExpectedUrl::builder()
545            .method(Method::PUT)
546            .endpoint("projects/simple%2Fproject/hooks/1")
547            .content_type("application/x-www-form-urlencoded")
548            .body_str("deployment_events=false")
549            .build()
550            .unwrap();
551        let client = SingleTestClient::new_raw(endpoint, "");
552
553        let endpoint = EditHook::builder()
554            .project("simple/project")
555            .hook_id(1)
556            .deployment_events(false)
557            .build()
558            .unwrap();
559        api::ignore(endpoint).query(&client).unwrap();
560    }
561
562    #[test]
563    fn endpoint_releases_events() {
564        let endpoint = ExpectedUrl::builder()
565            .method(Method::PUT)
566            .endpoint("projects/simple%2Fproject/hooks/1")
567            .content_type("application/x-www-form-urlencoded")
568            .body_str("releases_events=false")
569            .build()
570            .unwrap();
571        let client = SingleTestClient::new_raw(endpoint, "");
572
573        let endpoint = EditHook::builder()
574            .project("simple/project")
575            .hook_id(1)
576            .releases_events(false)
577            .build()
578            .unwrap();
579        api::ignore(endpoint).query(&client).unwrap();
580    }
581
582    #[test]
583    fn endpoint_feature_flag_events() {
584        let endpoint = ExpectedUrl::builder()
585            .method(Method::PUT)
586            .endpoint("projects/simple%2Fproject/hooks/1")
587            .content_type("application/x-www-form-urlencoded")
588            .body_str("feature_flag_events=true")
589            .build()
590            .unwrap();
591        let client = SingleTestClient::new_raw(endpoint, "");
592
593        let endpoint = EditHook::builder()
594            .project("simple/project")
595            .hook_id(1)
596            .feature_flag_events(true)
597            .build()
598            .unwrap();
599        api::ignore(endpoint).query(&client).unwrap();
600    }
601
602    #[test]
603    fn endpoint_resource_access_token_events() {
604        let endpoint = ExpectedUrl::builder()
605            .method(Method::PUT)
606            .endpoint("projects/simple%2Fproject/hooks/1")
607            .content_type("application/x-www-form-urlencoded")
608            .body_str("resource_access_token_events=true")
609            .build()
610            .unwrap();
611        let client = SingleTestClient::new_raw(endpoint, "");
612
613        let endpoint = EditHook::builder()
614            .project("simple/project")
615            .hook_id(1)
616            .resource_access_token_events(true)
617            .build()
618            .unwrap();
619        api::ignore(endpoint).query(&client).unwrap();
620    }
621
622    #[test]
623    fn endpoint_custom_webhook_template() {
624        let endpoint = ExpectedUrl::builder()
625            .method(Method::PUT)
626            .endpoint("projects/simple%2Fproject/hooks/1")
627            .content_type("application/x-www-form-urlencoded")
628            .body_str("custom_webhook_template=my-template-content")
629            .build()
630            .unwrap();
631        let client = SingleTestClient::new_raw(endpoint, "");
632
633        let endpoint = EditHook::builder()
634            .project("simple/project")
635            .hook_id(1)
636            .custom_webhook_template("my-template-content")
637            .build()
638            .unwrap();
639        api::ignore(endpoint).query(&client).unwrap();
640    }
641
642    #[test]
643    fn endpoint_custom_headers() {
644        let endpoint = ExpectedUrl::builder()
645            .method(Method::PUT)
646            .endpoint("projects/simple%2Fproject/hooks/1")
647            .content_type("application/x-www-form-urlencoded")
648            .body_str(concat!(
649                "custom_headers%5BX-Foo%5D=bar",
650                "&custom_headers%5BX-Another%5D=Value+With+Space",
651            ))
652            .build()
653            .unwrap();
654        let client = SingleTestClient::new_raw(endpoint, "");
655
656        let endpoint = EditHook::builder()
657            .project("simple/project")
658            .hook_id(1)
659            .custom_header("X-Foo", "bar")
660            .custom_headers([("X-Another", "Value With Space")].iter().cloned())
661            .build()
662            .unwrap();
663        api::ignore(endpoint).query(&client).unwrap();
664    }
665
666    #[test]
667    fn endpoint_enable_ssl_verification() {
668        let endpoint = ExpectedUrl::builder()
669            .method(Method::PUT)
670            .endpoint("projects/simple%2Fproject/hooks/1")
671            .content_type("application/x-www-form-urlencoded")
672            .body_str("enable_ssl_verification=false")
673            .build()
674            .unwrap();
675        let client = SingleTestClient::new_raw(endpoint, "");
676
677        let endpoint = EditHook::builder()
678            .project("simple/project")
679            .hook_id(1)
680            .enable_ssl_verification(false)
681            .build()
682            .unwrap();
683        api::ignore(endpoint).query(&client).unwrap();
684    }
685
686    #[test]
687    fn endpoint_token() {
688        let endpoint = ExpectedUrl::builder()
689            .method(Method::PUT)
690            .endpoint("projects/simple%2Fproject/hooks/1")
691            .content_type("application/x-www-form-urlencoded")
692            .body_str("token=secret")
693            .build()
694            .unwrap();
695        let client = SingleTestClient::new_raw(endpoint, "");
696
697        let endpoint = EditHook::builder()
698            .project("simple/project")
699            .hook_id(1)
700            .token("secret")
701            .build()
702            .unwrap();
703        api::ignore(endpoint).query(&client).unwrap();
704    }
705}