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