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