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 Nix,
392 Npm,
394 Nuget,
396 Opentofu,
398 Pip,
400 PreCommit,
402 Pub,
404 RustToolchain,
406 Swift,
408 Terraform,
410 Uv,
412 Vcpkg,
414}
415
416#[derive(Deserialize, Debug, Default, PartialEq)]
418#[serde(rename_all = "kebab-case")]
419pub enum RebaseStrategy {
420 #[default]
421 Auto,
422 Disabled,
423}
424
425#[derive(Deserialize, Debug)]
427#[serde(rename_all = "kebab-case", remote = "Self")]
428pub struct Schedule {
429 pub interval: Interval,
430 pub day: Option<Day>,
431 pub time: Option<String>,
432 pub timezone: Option<String>,
433 pub cronjob: Option<String>,
434}
435
436impl<'de> Deserialize<'de> for Schedule {
437 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
438 where
439 D: serde::Deserializer<'de>,
440 {
441 let schedule = Self::deserialize(deserializer)?;
442
443 if schedule.interval == Interval::Cron && schedule.cronjob.is_none() {
444 return Err(custom_error::<D>(
445 "`schedule.cronjob` must be set when `schedule.interval` is `cron`",
446 ));
447 }
448
449 if schedule.interval != Interval::Cron && schedule.cronjob.is_some() {
450 return Err(custom_error::<D>(
451 "`schedule.cronjob` may only be set when `schedule.interval` is `cron`",
452 ));
453 }
454
455 Ok(schedule)
461 }
462}
463
464#[derive(Deserialize, Debug, PartialEq)]
466#[serde(rename_all = "kebab-case")]
467pub enum Interval {
468 Daily,
469 Weekly,
470 Monthly,
471 Quarterly,
472 Semiannually,
473 Yearly,
474 Cron,
475}
476
477#[derive(Deserialize, Debug, PartialEq)]
479#[serde(rename_all = "kebab-case")]
480pub enum Day {
481 Monday,
482 Tuesday,
483 Wednesday,
484 Thursday,
485 Friday,
486 Saturday,
487 Sunday,
488}
489
490#[derive(Deserialize, Debug)]
492#[serde(rename_all = "kebab-case")]
493pub struct PullRequestBranchName {
494 pub separator: Option<String>,
495}
496
497#[derive(Deserialize, Debug, PartialEq)]
499#[serde(rename_all = "kebab-case")]
500pub enum VersioningStrategy {
501 Auto,
502 Increase,
503 IncreaseIfNecessary,
504 LockfileOnly,
505 Widen,
506}