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 Bun,
354 Bundler,
356 Cargo,
358 Composer,
360 Conda,
362 Devcontainers,
364 Docker,
366 DockerCompose,
368 DotnetSdk,
370 Helm,
372 Elm,
374 Gitsubmodule,
376 GithubActions,
378 Gomod,
380 Gradle,
382 Maven,
384 Mix,
386 Npm,
388 Nuget,
390 Pip,
392 Pub,
394 RustToolchain,
396 Swift,
398 Terraform,
400 Uv,
402 Vcpkg,
404}
405
406#[derive(Deserialize, Debug, Default, PartialEq)]
408#[serde(rename_all = "kebab-case")]
409pub enum RebaseStrategy {
410 #[default]
411 Auto,
412 Disabled,
413}
414
415#[derive(Deserialize, Debug)]
417#[serde(rename_all = "kebab-case", remote = "Self")]
418pub struct Schedule {
419 pub interval: Interval,
420 pub day: Option<Day>,
421 pub time: Option<String>,
422 pub timezone: Option<String>,
423 pub cronjob: Option<String>,
424}
425
426impl<'de> Deserialize<'de> for Schedule {
427 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
428 where
429 D: serde::Deserializer<'de>,
430 {
431 let schedule = Self::deserialize(deserializer)?;
432
433 if schedule.interval == Interval::Cron && schedule.cronjob.is_none() {
434 return Err(custom_error::<D>(
435 "`schedule.cronjob` must be set when `schedule.interval` is `cron`",
436 ));
437 }
438
439 if schedule.interval != Interval::Cron && schedule.cronjob.is_some() {
440 return Err(custom_error::<D>(
441 "`schedule.cronjob` may only be set when `schedule.interval` is `cron`",
442 ));
443 }
444
445 Ok(schedule)
451 }
452}
453
454#[derive(Deserialize, Debug, PartialEq)]
456#[serde(rename_all = "kebab-case")]
457pub enum Interval {
458 Daily,
459 Weekly,
460 Monthly,
461 Quarterly,
462 Semiannually,
463 Yearly,
464 Cron,
465}
466
467#[derive(Deserialize, Debug, PartialEq)]
469#[serde(rename_all = "kebab-case")]
470pub enum Day {
471 Monday,
472 Tuesday,
473 Wednesday,
474 Thursday,
475 Friday,
476 Saturday,
477 Sunday,
478}
479
480#[derive(Deserialize, Debug)]
482#[serde(rename_all = "kebab-case")]
483pub struct PullRequestBranchName {
484 pub separator: Option<String>,
485}
486
487#[derive(Deserialize, Debug, PartialEq)]
489#[serde(rename_all = "kebab-case")]
490pub enum VersioningStrategy {
491 Auto,
492 Increase,
493 IncreaseIfNecessary,
494 LockfileOnly,
495 Widen,
496}