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 CargoRegistry {
47 url: String,
48 registry: String,
49 token: String,
50 },
51 ComposerRepository {
52 url: String,
53 username: Option<String>,
54 password: Option<String>,
55 },
56 DockerRegistry {
57 url: String,
58 username: Option<String>,
59 password: Option<String>,
60 #[serde(default)]
61 replaces_base: bool,
62 },
63 Git {
64 url: String,
65 username: Option<String>,
66 password: Option<String>,
67 },
68 HexOrganization {
69 organization: String,
70 key: Option<String>,
71 },
72 HexRepository {
73 repo: Option<String>,
74 url: String,
75 auth_key: Option<String>,
76 public_key_fingerprint: Option<String>,
77 },
78 MavenRepository {
79 url: String,
80 username: Option<String>,
81 password: Option<String>,
82 },
83 NpmRegistry {
84 url: String,
85 username: Option<String>,
86 password: Option<String>,
87 #[serde(default)]
88 replaces_base: bool,
89 },
90 NugetFeed {
91 url: String,
92 username: Option<String>,
93 password: Option<String>,
94 },
95 PythonIndex {
96 url: String,
97 username: Option<String>,
98 password: Option<String>,
99 #[serde(default)]
100 replaces_base: bool,
101 },
102 RubygemsServer {
103 url: String,
104 username: Option<String>,
105 password: Option<String>,
106 #[serde(default)]
107 replaces_base: bool,
108 },
109 TerraformRegistry {
110 url: String,
111 token: Option<String>,
112 },
113}
114
115#[derive(Deserialize, Debug)]
117#[serde(rename_all = "kebab-case")]
118pub struct Cooldown {
119 pub default_days: Option<u64>,
120 pub semver_major_days: Option<u64>,
121 pub semver_minor_days: Option<u64>,
122 pub semver_patch_days: Option<u64>,
123 #[serde(default)]
124 pub include: Vec<String>,
125 #[serde(default)]
126 pub exclude: Vec<String>,
127}
128
129#[derive(Deserialize, Debug, PartialEq)]
131#[serde(rename_all = "kebab-case")]
132pub enum Directories {
133 Directory(String),
134 Directories(Vec<String>),
135}
136
137#[derive(Deserialize, Debug)]
139#[serde(rename_all = "kebab-case", remote = "Self")]
140pub struct Update {
141 #[serde(default)]
143 pub allow: Vec<Allow>,
144
145 #[serde(default)]
147 pub assignees: IndexSet<String>,
148
149 pub commit_message: Option<CommitMessage>,
151
152 pub cooldown: Option<Cooldown>,
154
155 #[serde(flatten)]
158 pub directories: Directories,
159
160 #[serde(default)]
162 pub groups: IndexMap<String, Group>,
163
164 #[serde(default)]
166 pub ignore: Vec<Ignore>,
167
168 #[serde(default)]
170 pub insecure_external_code_execution: AllowDeny,
171
172 #[serde(default = "default_labels")]
176 pub labels: IndexSet<String>,
177 pub milestone: Option<u64>,
178 #[serde(default = "default_open_pull_requests_limit")]
183 pub open_pull_requests_limit: u64,
184
185 pub package_ecosystem: PackageEcosystem,
187
188 #[serde(default)]
190 pub rebase_strategy: RebaseStrategy,
191 #[serde(default, deserialize_with = "crate::common::scalar_or_vector")]
192 pub registries: Vec<String>,
193 #[serde(default)]
194 pub reviewers: IndexSet<String>,
195 pub schedule: Option<Schedule>,
196 pub target_branch: Option<String>,
197 pub pull_request_branch_name: Option<PullRequestBranchName>,
198 #[serde(default)]
199 pub vendor: bool,
200 pub versioning_strategy: Option<VersioningStrategy>,
201
202 pub multi_ecosystem_group: Option<String>,
207
208 pub patterns: Option<IndexSet<String>>,
214
215 #[serde(default)]
220 pub exclude_paths: Option<IndexSet<String>>,
221}
222
223impl<'de> Deserialize<'de> for Update {
224 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
225 where
226 D: serde::Deserializer<'de>,
227 {
228 let update = Self::deserialize(deserializer)?;
229
230 if update.multi_ecosystem_group.is_some() && update.patterns.is_none() {
232 return Err(custom_error::<D>(
233 "`patterns` must be set when `multi-ecosystem-group` is set",
234 ));
235 }
236
237 if update.multi_ecosystem_group.is_some() {
241 if update.milestone.is_some() {
242 return Err(custom_error::<D>(
243 "`milestone` may not be set when `multi-ecosystem-group` is set",
244 ));
245 }
246 if update.target_branch.is_some() {
247 return Err(custom_error::<D>(
248 "`target-branch` may not be set when `multi-ecosystem-group` is set",
249 ));
250 }
251 if update.commit_message.is_some() {
252 return Err(custom_error::<D>(
253 "`commit-message` may not be set when `multi-ecosystem-group` is set",
254 ));
255 }
256 if update.pull_request_branch_name.is_some() {
257 return Err(custom_error::<D>(
258 "`pull-request-branch-name` may not be set when `multi-ecosystem-group` is set",
259 ));
260 }
261 }
262
263 Ok(update)
264 }
265}
266
267#[inline]
268fn default_labels() -> IndexSet<String> {
269 IndexSet::from(["dependencies".to_string()])
270}
271
272#[inline]
273fn default_open_pull_requests_limit() -> u64 {
274 5
276}
277
278#[derive(Deserialize, Debug)]
280#[serde(rename_all = "kebab-case")]
281pub struct Allow {
282 pub dependency_name: Option<String>,
283 pub dependency_type: Option<DependencyType>,
284}
285
286#[derive(Deserialize, Debug)]
288#[serde(rename_all = "kebab-case")]
289pub enum DependencyType {
290 Direct,
291 Indirect,
292 All,
293 Production,
294 Development,
295}
296
297#[derive(Deserialize, Debug)]
299#[serde(rename_all = "kebab-case")]
300pub struct CommitMessage {
301 pub prefix: Option<String>,
302 pub prefix_development: Option<String>,
303 pub include: Option<String>,
305}
306
307#[derive(Deserialize, Debug)]
309#[serde(rename_all = "kebab-case")]
310pub struct Group {
311 pub dependency_type: Option<DependencyType>,
314 #[serde(default)]
315 pub patterns: IndexSet<String>,
316 #[serde(default)]
317 pub exclude_patterns: IndexSet<String>,
318 #[serde(default)]
319 pub update_types: IndexSet<UpdateType>,
320}
321
322#[derive(Deserialize, Debug, Hash, Eq, PartialEq)]
324#[serde(rename_all = "kebab-case")]
325pub enum UpdateType {
326 Major,
327 Minor,
328 Patch,
329}
330
331#[derive(Deserialize, Debug)]
333#[serde(rename_all = "kebab-case")]
334pub struct Ignore {
335 pub dependency_name: Option<String>,
336 #[serde(default)]
339 pub update_types: IndexSet<String>,
340 #[serde(default)]
341 pub versions: IndexSet<String>,
342}
343
344#[derive(Deserialize, Debug, Default)]
346#[serde(rename_all = "kebab-case")]
347pub enum AllowDeny {
348 Allow,
349 #[default]
350 Deny,
351}
352
353#[derive(Deserialize, Debug, PartialEq)]
355#[serde(rename_all = "kebab-case")]
356pub enum PackageEcosystem {
357 Bazel,
359 Bun,
361 Bundler,
363 Cargo,
365 Composer,
367 Conda,
369 Deno,
371 Devcontainers,
373 Docker,
375 DockerCompose,
377 DotnetSdk,
379 Helm,
381 Julia,
383 Elm,
385 Gitsubmodule,
387 GithubActions,
389 Gomod,
391 Gradle,
393 Maven,
395 Mix,
397 Nix,
399 Npm,
401 Nuget,
403 Opentofu,
405 Pip,
407 PreCommit,
409 Pub,
411 RustToolchain,
413 Sbt,
415 Swift,
417 Terraform,
419 Uv,
421 Vcpkg,
423}
424
425#[derive(Deserialize, Debug, Default, PartialEq)]
427#[serde(rename_all = "kebab-case")]
428pub enum RebaseStrategy {
429 #[default]
430 Auto,
431 Disabled,
432}
433
434#[derive(Deserialize, Debug)]
436#[serde(rename_all = "kebab-case", remote = "Self")]
437pub struct Schedule {
438 pub interval: Interval,
439 pub day: Option<Day>,
440 pub time: Option<String>,
441 pub timezone: Option<String>,
442 pub cronjob: Option<String>,
443}
444
445impl<'de> Deserialize<'de> for Schedule {
446 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
447 where
448 D: serde::Deserializer<'de>,
449 {
450 let schedule = Self::deserialize(deserializer)?;
451
452 if schedule.interval == Interval::Cron && schedule.cronjob.is_none() {
453 return Err(custom_error::<D>(
454 "`schedule.cronjob` must be set when `schedule.interval` is `cron`",
455 ));
456 }
457
458 if schedule.interval != Interval::Cron && schedule.cronjob.is_some() {
459 return Err(custom_error::<D>(
460 "`schedule.cronjob` may only be set when `schedule.interval` is `cron`",
461 ));
462 }
463
464 Ok(schedule)
470 }
471}
472
473#[derive(Deserialize, Debug, PartialEq)]
475#[serde(rename_all = "kebab-case")]
476pub enum Interval {
477 Daily,
478 Weekly,
479 Monthly,
480 Quarterly,
481 Semiannually,
482 Yearly,
483 Cron,
484}
485
486#[derive(Deserialize, Debug, PartialEq)]
488#[serde(rename_all = "kebab-case")]
489pub enum Day {
490 Monday,
491 Tuesday,
492 Wednesday,
493 Thursday,
494 Friday,
495 Saturday,
496 Sunday,
497}
498
499#[derive(Deserialize, Debug)]
501#[serde(rename_all = "kebab-case")]
502pub struct PullRequestBranchName {
503 pub separator: Option<String>,
504}
505
506#[derive(Deserialize, Debug, PartialEq)]
508#[serde(rename_all = "kebab-case")]
509pub enum VersioningStrategy {
510 Auto,
511 Increase,
512 IncreaseIfNecessary,
513 LockfileOnly,
514 Widen,
515}