1use indexmap::{IndexMap, IndexSet};
8use serde::Deserialize;
9
10use crate::common::custom_error;
11
12#[derive(Deserialize, Debug)]
14#[serde(rename_all = "kebab-case")]
15pub struct Dependabot {
16 pub version: u64,
18 #[serde(default)]
19 pub enable_beta_ecosystems: bool,
20 #[serde(default)]
21 pub multi_ecosystem_groups: IndexMap<String, MultiEcosystemGroup>,
22 #[serde(default)]
23 pub registries: IndexMap<String, Registry>,
24 pub updates: Vec<Update>,
25}
26
27#[derive(Deserialize, Debug)]
29#[serde(rename_all = "kebab-case")]
30pub struct MultiEcosystemGroup {
31 pub schedule: Schedule,
32 #[serde(default = "default_labels")]
33 pub labels: IndexSet<String>,
34 pub milestone: Option<u64>,
35 #[serde(default)]
36 pub assignees: IndexSet<String>,
37 pub target_branch: Option<String>,
38 pub commit_message: Option<CommitMessage>,
39 pub pull_request_branch_name: Option<PullRequestBranchName>,
40}
41
42#[derive(Deserialize, Debug)]
44#[serde(rename_all = "kebab-case", tag = "type")]
45pub enum Registry {
46 ComposerRepository {
47 url: String,
48 username: Option<String>,
49 password: Option<String>,
50 },
51 DockerRegistry {
52 url: String,
53 username: Option<String>,
54 password: Option<String>,
55 #[serde(default)]
56 replaces_base: bool,
57 },
58 Git {
59 url: String,
60 username: Option<String>,
61 password: Option<String>,
62 },
63 HexOrganization {
64 organization: String,
65 key: Option<String>,
66 },
67 HexRepository {
68 repo: Option<String>,
69 url: String,
70 auth_key: Option<String>,
71 public_key_fingerprint: Option<String>,
72 },
73 MavenRepository {
74 url: String,
75 username: Option<String>,
76 password: Option<String>,
77 },
78 NpmRegistry {
79 url: String,
80 username: Option<String>,
81 password: Option<String>,
82 #[serde(default)]
83 replaces_base: bool,
84 },
85 NugetFeed {
86 url: String,
87 username: Option<String>,
88 password: Option<String>,
89 },
90 PythonIndex {
91 url: String,
92 username: Option<String>,
93 password: Option<String>,
94 #[serde(default)]
95 replaces_base: bool,
96 },
97 RubygemsServer {
98 url: String,
99 username: Option<String>,
100 password: Option<String>,
101 #[serde(default)]
102 replaces_base: bool,
103 },
104 TerraformRegistry {
105 url: String,
106 token: Option<String>,
107 },
108}
109
110#[derive(Deserialize, Debug)]
112#[serde(rename_all = "kebab-case")]
113pub struct Cooldown {
114 pub default_days: Option<u64>,
115 pub semver_major_days: Option<u64>,
116 pub semver_minor_days: Option<u64>,
117 pub semver_patch_days: Option<u64>,
118 #[serde(default)]
119 pub include: Vec<String>,
120 #[serde(default)]
121 pub exclude: Vec<String>,
122}
123
124#[derive(Deserialize, Debug, PartialEq)]
126#[serde(rename_all = "kebab-case")]
127pub enum Directories {
128 Directory(String),
129 Directories(Vec<String>),
130}
131
132#[derive(Deserialize, Debug)]
134#[serde(rename_all = "kebab-case", remote = "Self")]
135pub struct Update {
136 #[serde(default)]
138 pub allow: Vec<Allow>,
139
140 #[serde(default)]
142 pub assignees: IndexSet<String>,
143
144 pub commit_message: Option<CommitMessage>,
146
147 pub cooldown: Option<Cooldown>,
149
150 #[serde(flatten)]
153 pub directories: Directories,
154
155 #[serde(default)]
157 pub groups: IndexMap<String, Group>,
158
159 #[serde(default)]
161 pub ignore: Vec<Ignore>,
162
163 #[serde(default)]
165 pub insecure_external_code_execution: AllowDeny,
166
167 #[serde(default = "default_labels")]
171 pub labels: IndexSet<String>,
172 pub milestone: Option<u64>,
173 #[serde(default = "default_open_pull_requests_limit")]
178 pub open_pull_requests_limit: u64,
179
180 pub package_ecosystem: PackageEcosystem,
182
183 #[serde(default)]
185 pub rebase_strategy: RebaseStrategy,
186 #[serde(default, deserialize_with = "crate::common::scalar_or_vector")]
187 pub registries: Vec<String>,
188 #[serde(default)]
189 pub reviewers: IndexSet<String>,
190 pub schedule: Option<Schedule>,
191 pub target_branch: Option<String>,
192 pub pull_request_branch_name: Option<PullRequestBranchName>,
193 #[serde(default)]
194 pub vendor: bool,
195 pub versioning_strategy: Option<VersioningStrategy>,
196
197 pub multi_ecosystem_group: Option<String>,
202
203 pub patterns: Option<IndexSet<String>>,
209
210 #[serde(default)]
215 pub exclude_paths: Option<IndexSet<String>>,
216}
217
218impl<'de> Deserialize<'de> for Update {
219 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
220 where
221 D: serde::Deserializer<'de>,
222 {
223 let update = Self::deserialize(deserializer)?;
224
225 if update.multi_ecosystem_group.is_some() && update.patterns.is_none() {
227 return Err(custom_error::<D>(
228 "`patterns` must be set when `multi-ecosystem-group` is set",
229 ));
230 }
231
232 if update.multi_ecosystem_group.is_some() {
236 if update.milestone.is_some() {
237 return Err(custom_error::<D>(
238 "`milestone` may not be set when `multi-ecosystem-group` is set",
239 ));
240 }
241 if update.target_branch.is_some() {
242 return Err(custom_error::<D>(
243 "`target-branch` may not be set when `multi-ecosystem-group` is set",
244 ));
245 }
246 if update.commit_message.is_some() {
247 return Err(custom_error::<D>(
248 "`commit-message` may not be set when `multi-ecosystem-group` is set",
249 ));
250 }
251 if update.pull_request_branch_name.is_some() {
252 return Err(custom_error::<D>(
253 "`pull-request-branch-name` may not be set when `multi-ecosystem-group` is set",
254 ));
255 }
256 }
257
258 Ok(update)
259 }
260}
261
262#[inline]
263fn default_labels() -> IndexSet<String> {
264 IndexSet::from(["dependencies".to_string()])
265}
266
267#[inline]
268fn default_open_pull_requests_limit() -> u64 {
269 5
271}
272
273#[derive(Deserialize, Debug)]
275#[serde(rename_all = "kebab-case")]
276pub struct Allow {
277 pub dependency_name: Option<String>,
278 pub dependency_type: Option<DependencyType>,
279}
280
281#[derive(Deserialize, Debug)]
283#[serde(rename_all = "kebab-case")]
284pub enum DependencyType {
285 Direct,
286 Indirect,
287 All,
288 Production,
289 Development,
290}
291
292#[derive(Deserialize, Debug)]
294#[serde(rename_all = "kebab-case")]
295pub struct CommitMessage {
296 pub prefix: Option<String>,
297 pub prefix_development: Option<String>,
298 pub include: Option<String>,
300}
301
302#[derive(Deserialize, Debug)]
304#[serde(rename_all = "kebab-case")]
305pub struct Group {
306 pub dependency_type: Option<DependencyType>,
309 #[serde(default)]
310 pub patterns: IndexSet<String>,
311 #[serde(default)]
312 pub exclude_patterns: IndexSet<String>,
313 #[serde(default)]
314 pub update_types: IndexSet<UpdateType>,
315}
316
317#[derive(Deserialize, Debug, Hash, Eq, PartialEq)]
319#[serde(rename_all = "kebab-case")]
320pub enum UpdateType {
321 Major,
322 Minor,
323 Patch,
324}
325
326#[derive(Deserialize, Debug)]
328#[serde(rename_all = "kebab-case")]
329pub struct Ignore {
330 pub dependency_name: Option<String>,
331 #[serde(default)]
334 pub update_types: IndexSet<String>,
335 #[serde(default)]
336 pub versions: IndexSet<String>,
337}
338
339#[derive(Deserialize, Debug, Default)]
341#[serde(rename_all = "kebab-case")]
342pub enum AllowDeny {
343 Allow,
344 #[default]
345 Deny,
346}
347
348#[derive(Deserialize, Debug, PartialEq)]
350#[serde(rename_all = "kebab-case")]
351pub enum PackageEcosystem {
352 Bazel,
354 Bun,
356 Bundler,
358 Cargo,
360 Composer,
362 Conda,
364 Devcontainers,
366 Docker,
368 DockerCompose,
370 DotnetSdk,
372 Helm,
374 Julia,
376 Elm,
378 Gitsubmodule,
380 GithubActions,
382 Gomod,
384 Gradle,
386 Maven,
388 Mix,
390 Npm,
392 Nuget,
394 Opentofu,
396 Pip,
398 Pub,
400 RustToolchain,
402 Swift,
404 Terraform,
406 Uv,
408 Vcpkg,
410}
411
412#[derive(Deserialize, Debug, Default, PartialEq)]
414#[serde(rename_all = "kebab-case")]
415pub enum RebaseStrategy {
416 #[default]
417 Auto,
418 Disabled,
419}
420
421#[derive(Deserialize, Debug)]
423#[serde(rename_all = "kebab-case", remote = "Self")]
424pub struct Schedule {
425 pub interval: Interval,
426 pub day: Option<Day>,
427 pub time: Option<String>,
428 pub timezone: Option<String>,
429 pub cronjob: Option<String>,
430}
431
432impl<'de> Deserialize<'de> for Schedule {
433 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
434 where
435 D: serde::Deserializer<'de>,
436 {
437 let schedule = Self::deserialize(deserializer)?;
438
439 if schedule.interval == Interval::Cron && schedule.cronjob.is_none() {
440 return Err(custom_error::<D>(
441 "`schedule.cronjob` must be set when `schedule.interval` is `cron`",
442 ));
443 }
444
445 if schedule.interval != Interval::Cron && schedule.cronjob.is_some() {
446 return Err(custom_error::<D>(
447 "`schedule.cronjob` may only be set when `schedule.interval` is `cron`",
448 ));
449 }
450
451 Ok(schedule)
457 }
458}
459
460#[derive(Deserialize, Debug, PartialEq)]
462#[serde(rename_all = "kebab-case")]
463pub enum Interval {
464 Daily,
465 Weekly,
466 Monthly,
467 Quarterly,
468 Semiannually,
469 Yearly,
470 Cron,
471}
472
473#[derive(Deserialize, Debug, PartialEq)]
475#[serde(rename_all = "kebab-case")]
476pub enum Day {
477 Monday,
478 Tuesday,
479 Wednesday,
480 Thursday,
481 Friday,
482 Saturday,
483 Sunday,
484}
485
486#[derive(Deserialize, Debug)]
488#[serde(rename_all = "kebab-case")]
489pub struct PullRequestBranchName {
490 pub separator: Option<String>,
491}
492
493#[derive(Deserialize, Debug, PartialEq)]
495#[serde(rename_all = "kebab-case")]
496pub enum VersioningStrategy {
497 Auto,
498 Increase,
499 IncreaseIfNecessary,
500 LockfileOnly,
501 Widen,
502}