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 GoproxyServer {
69 url: String,
70 username: Option<String>,
71 password: Option<String>,
72 },
73 HexOrganization {
74 organization: String,
75 key: Option<String>,
76 },
77 HexRepository {
78 repo: Option<String>,
79 url: String,
80 auth_key: Option<String>,
81 public_key_fingerprint: Option<String>,
82 },
83 MavenRepository {
84 url: String,
85 username: Option<String>,
86 password: Option<String>,
87 },
88 NpmRegistry {
89 url: String,
90 username: Option<String>,
91 password: Option<String>,
92 #[serde(default)]
93 replaces_base: bool,
94 },
95 NugetFeed {
96 url: String,
97 username: Option<String>,
98 password: Option<String>,
99 },
100 PythonIndex {
101 url: String,
102 username: Option<String>,
103 password: Option<String>,
104 #[serde(default)]
105 replaces_base: bool,
106 },
107 RubygemsServer {
108 url: String,
109 username: Option<String>,
110 password: Option<String>,
111 #[serde(default)]
112 replaces_base: bool,
113 },
114 TerraformRegistry {
115 url: String,
116 token: Option<String>,
117 },
118}
119
120#[derive(Deserialize, Debug)]
122#[serde(rename_all = "kebab-case")]
123pub struct Cooldown {
124 pub default_days: Option<u64>,
125 pub semver_major_days: Option<u64>,
126 pub semver_minor_days: Option<u64>,
127 pub semver_patch_days: Option<u64>,
128 #[serde(default)]
129 pub include: Vec<String>,
130 #[serde(default)]
131 pub exclude: Vec<String>,
132}
133
134#[derive(Deserialize, Debug, PartialEq)]
136#[serde(rename_all = "kebab-case")]
137pub enum Directories {
138 Directory(String),
139 Directories(Vec<String>),
140}
141
142#[derive(Deserialize, Debug)]
144#[serde(rename_all = "kebab-case", remote = "Self")]
145pub struct Update {
146 #[serde(default)]
148 pub allow: Vec<Allow>,
149
150 #[serde(default)]
152 pub assignees: IndexSet<String>,
153
154 pub commit_message: Option<CommitMessage>,
156
157 pub cooldown: Option<Cooldown>,
159
160 #[serde(flatten)]
163 pub directories: Directories,
164
165 #[serde(default)]
167 pub groups: IndexMap<String, Group>,
168
169 #[serde(default)]
171 pub ignore: Vec<Ignore>,
172
173 #[serde(default)]
175 pub insecure_external_code_execution: AllowDeny,
176
177 #[serde(default = "default_labels")]
181 pub labels: IndexSet<String>,
182 pub milestone: Option<u64>,
183 #[serde(default = "default_open_pull_requests_limit")]
188 pub open_pull_requests_limit: u64,
189
190 pub package_ecosystem: PackageEcosystem,
192
193 #[serde(default)]
195 pub rebase_strategy: RebaseStrategy,
196 #[serde(default, deserialize_with = "crate::common::scalar_or_vector")]
197 pub registries: Vec<String>,
198 #[serde(default)]
199 pub reviewers: IndexSet<String>,
200 pub schedule: Option<Schedule>,
201 pub target_branch: Option<String>,
202 pub pull_request_branch_name: Option<PullRequestBranchName>,
203 #[serde(default)]
204 pub vendor: bool,
205 pub versioning_strategy: Option<VersioningStrategy>,
206
207 pub multi_ecosystem_group: Option<String>,
212
213 pub patterns: Option<IndexSet<String>>,
219
220 #[serde(default)]
225 pub exclude_paths: Option<IndexSet<String>>,
226}
227
228impl<'de> Deserialize<'de> for Update {
229 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
230 where
231 D: serde::Deserializer<'de>,
232 {
233 let update = Self::deserialize(deserializer)?;
234
235 if update.multi_ecosystem_group.is_some() && update.patterns.is_none() {
237 return Err(custom_error::<D>(
238 "`patterns` must be set when `multi-ecosystem-group` is set",
239 ));
240 }
241
242 if update.multi_ecosystem_group.is_some() {
246 if update.milestone.is_some() {
247 return Err(custom_error::<D>(
248 "`milestone` may not be set when `multi-ecosystem-group` is set",
249 ));
250 }
251 if update.target_branch.is_some() {
252 return Err(custom_error::<D>(
253 "`target-branch` may not be set when `multi-ecosystem-group` is set",
254 ));
255 }
256 if update.commit_message.is_some() {
257 return Err(custom_error::<D>(
258 "`commit-message` may not be set when `multi-ecosystem-group` is set",
259 ));
260 }
261 if update.pull_request_branch_name.is_some() {
262 return Err(custom_error::<D>(
263 "`pull-request-branch-name` may not be set when `multi-ecosystem-group` is set",
264 ));
265 }
266 }
267
268 Ok(update)
269 }
270}
271
272#[inline]
273fn default_labels() -> IndexSet<String> {
274 IndexSet::from(["dependencies".to_string()])
275}
276
277#[inline]
278fn default_open_pull_requests_limit() -> u64 {
279 5
281}
282
283#[derive(Deserialize, Debug)]
285#[serde(rename_all = "kebab-case")]
286pub struct Allow {
287 pub dependency_name: Option<String>,
288 pub dependency_type: Option<DependencyType>,
289}
290
291#[derive(Deserialize, Debug)]
293#[serde(rename_all = "kebab-case")]
294pub enum DependencyType {
295 Direct,
296 Indirect,
297 All,
298 Production,
299 Development,
300}
301
302#[derive(Deserialize, Debug)]
304#[serde(rename_all = "kebab-case")]
305pub struct CommitMessage {
306 pub prefix: Option<String>,
307 pub prefix_development: Option<String>,
308 pub include: Option<String>,
310}
311
312#[derive(Deserialize, Debug)]
314#[serde(rename_all = "kebab-case")]
315pub struct Group {
316 pub dependency_type: Option<DependencyType>,
319 #[serde(default)]
320 pub patterns: IndexSet<String>,
321 #[serde(default)]
322 pub exclude_patterns: IndexSet<String>,
323 #[serde(default)]
324 pub update_types: IndexSet<UpdateType>,
325}
326
327#[derive(Deserialize, Debug, Hash, Eq, PartialEq)]
329#[serde(rename_all = "kebab-case")]
330pub enum UpdateType {
331 Major,
332 Minor,
333 Patch,
334}
335
336#[derive(Deserialize, Debug)]
338#[serde(rename_all = "kebab-case")]
339pub struct Ignore {
340 pub dependency_name: Option<String>,
341 #[serde(default)]
344 pub update_types: IndexSet<String>,
345 #[serde(default)]
346 pub versions: IndexSet<String>,
347}
348
349#[derive(Deserialize, Debug, Default)]
351#[serde(rename_all = "kebab-case")]
352pub enum AllowDeny {
353 Allow,
354 #[default]
355 Deny,
356}
357
358#[derive(Deserialize, Debug, PartialEq)]
360#[serde(rename_all = "kebab-case")]
361pub enum PackageEcosystem {
362 Bazel,
364 Bun,
366 Bundler,
368 Cargo,
370 Composer,
372 Conda,
374 Deno,
376 Devcontainers,
378 Docker,
380 DockerCompose,
382 DotnetSdk,
384 Helm,
386 Julia,
388 Elm,
390 Gitsubmodule,
392 GithubActions,
394 Gomod,
396 Gradle,
398 Maven,
400 Mix,
402 Nix,
404 Npm,
406 Nuget,
408 Opentofu,
410 Pip,
412 PreCommit,
414 Pub,
416 RustToolchain,
418 Sbt,
420 Swift,
422 Terraform,
424 Uv,
426 Vcpkg,
428}
429
430#[derive(Deserialize, Debug, Default, PartialEq)]
432#[serde(rename_all = "kebab-case")]
433pub enum RebaseStrategy {
434 #[default]
435 Auto,
436 Disabled,
437}
438
439#[derive(Deserialize, Debug)]
441#[serde(rename_all = "kebab-case", remote = "Self")]
442pub struct Schedule {
443 pub interval: Interval,
444 pub day: Option<Day>,
445 pub time: Option<String>,
446 pub timezone: Option<String>,
447 pub cronjob: Option<String>,
448}
449
450impl<'de> Deserialize<'de> for Schedule {
451 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
452 where
453 D: serde::Deserializer<'de>,
454 {
455 let schedule = Self::deserialize(deserializer)?;
456
457 if schedule.interval == Interval::Cron && schedule.cronjob.is_none() {
458 return Err(custom_error::<D>(
459 "`schedule.cronjob` must be set when `schedule.interval` is `cron`",
460 ));
461 }
462
463 if schedule.interval != Interval::Cron && schedule.cronjob.is_some() {
464 return Err(custom_error::<D>(
465 "`schedule.cronjob` may only be set when `schedule.interval` is `cron`",
466 ));
467 }
468
469 Ok(schedule)
475 }
476}
477
478#[derive(Deserialize, Debug, PartialEq)]
480#[serde(rename_all = "kebab-case")]
481pub enum Interval {
482 Daily,
483 Weekly,
484 Monthly,
485 Quarterly,
486 Semiannually,
487 Yearly,
488 Cron,
489}
490
491#[derive(Deserialize, Debug, PartialEq)]
493#[serde(rename_all = "kebab-case")]
494pub enum Day {
495 Monday,
496 Tuesday,
497 Wednesday,
498 Thursday,
499 Friday,
500 Saturday,
501 Sunday,
502}
503
504#[derive(Deserialize, Debug)]
506#[serde(rename_all = "kebab-case")]
507pub struct PullRequestBranchName {
508 pub separator: Option<String>,
509}
510
511#[derive(Deserialize, Debug, PartialEq)]
513#[serde(rename_all = "kebab-case")]
514pub enum VersioningStrategy {
515 Auto,
516 Increase,
517 IncreaseIfNecessary,
518 LockfileOnly,
519 Widen,
520}