ephemeris_forge_forgejo/plan/
webhooks.rs1use ephemeris_forge::{Collection, FieldDiff, NameSet, Report};
2
3use crate::Live;
4use crate::api::model::Hook;
5use crate::api::request::{CreateHookOption, EditHookOption};
6use crate::config::{Webhook, Webhooks};
7use crate::operation::webhooks::Operation;
8
9pub(super) fn plan(config: Option<&Webhooks>, live: &Live, report: &mut Report) -> Vec<Operation> {
10 let Some(config) = config else {
11 return Vec::new();
12 };
13 Collection {
14 noun: "webhook",
15 mode: config.mode,
16 }
17 .plan(
18 &config.webhooks,
19 &live.webhooks,
20 report,
21 |hook, url| hook.url.as_deref() == Some(url),
22 |url, desired, actual, _| {
23 let mut diff = FieldDiff::default();
24 let kind_differs = diff.check(
25 "type",
26 actual.is_some_and(|hook| hook.kind.as_deref() == Some(desired.kind.as_str())),
27 &actual.and_then(|hook| hook.kind.as_deref()),
28 &desired.kind.as_str(),
29 );
30 let recreate = kind_differs && actual.is_some();
31 let edit = compare(desired, actual, &mut diff);
32 let operations = match actual {
33 Some(hook) if recreate => vec![
34 Operation::Delete {
35 id: hook.id,
36 url: url.to_owned(),
37 },
38 create(url, desired),
39 ],
40 Some(hook) if !edit.is_empty() => vec![Operation::Patch {
41 id: hook.id,
42 url: url.to_owned(),
43 edit: with_kept_fields(edit, desired, hook),
44 }],
45 Some(_) => Vec::new(),
46 None => vec![create(url, desired)],
47 };
48 (diff, operations)
49 },
50 |hook, _| {
51 let url = hook.url.clone().unwrap_or_else(|| format!("#{}", hook.id));
52 Some((url.clone(), Operation::Delete { id: hook.id, url }))
53 },
54 )
55}
56
57fn create(url: &str, desired: &Webhook) -> Operation {
58 Operation::Create {
59 url: url.to_owned(),
60 body: CreateHookOption::new(url, desired),
61 }
62}
63
64fn compare(desired: &Webhook, actual: Option<&Hook>, diff: &mut FieldDiff) -> EditHookOption {
66 let mut edit = EditHookOption::default();
67 let content_type = desired.content_type.as_str().to_owned();
68 let live_content_type = actual.and_then(|hook| hook.content_type.clone());
69 if let Some(wanted) = diff.want("content_type", &content_type, live_content_type.as_ref()) {
70 edit.config = Some([("content_type".to_owned(), wanted)].into());
71 }
72 let events = actual.map(|hook| NameSet::new(hook.events.clone().unwrap_or_default()));
73 edit.events = diff
74 .want_set(
75 "events",
76 &NameSet::new(desired.events.clone()),
77 events.as_ref(),
78 )
79 .map(NameSet::into_vec);
80 let active = actual.map(|hook| hook.active.unwrap_or(false));
81 edit.active = diff.want("active", &desired.active, active.as_ref());
82 let branch_filter = actual.map(|hook| hook.branch_filter.clone().unwrap_or_default());
83 edit.branch_filter = diff.want(
84 "branch_filter",
85 &desired.branch_filter,
86 branch_filter.as_ref(),
87 );
88 edit
89}
90
91fn with_kept_fields(mut edit: EditHookOption, desired: &Webhook, actual: &Hook) -> EditHookOption {
94 edit.events.get_or_insert_with(|| desired.events.clone());
95 edit.branch_filter
96 .get_or_insert_with(|| desired.branch_filter.clone());
97 edit.authorization_header = actual
98 .authorization_header
99 .clone()
100 .filter(|header| !header.is_empty());
101 edit
102}