use indexmap::{IndexMap, IndexSet};
use serde::Deserialize;
use crate::common::custom_error;
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Dependabot {
pub version: u64,
#[serde(default)]
pub enable_beta_ecosystems: bool,
#[serde(default)]
pub multi_ecosystem_groups: IndexMap<String, MultiEcosystemGroup>,
#[serde(default)]
pub registries: IndexMap<String, Registry>,
pub updates: Vec<Update>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct MultiEcosystemGroup {
pub schedule: Schedule,
#[serde(default = "default_labels")]
pub labels: IndexSet<String>,
pub milestone: Option<u64>,
#[serde(default)]
pub assignees: IndexSet<String>,
pub target_branch: Option<String>,
pub commit_message: Option<CommitMessage>,
pub pull_request_branch_name: Option<PullRequestBranchName>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case", tag = "type")]
pub enum Registry {
ComposerRepository {
url: String,
username: Option<String>,
password: Option<String>,
},
DockerRegistry {
url: String,
username: Option<String>,
password: Option<String>,
#[serde(default)]
replaces_base: bool,
},
Git {
url: String,
username: Option<String>,
password: Option<String>,
},
HexOrganization {
organization: String,
key: Option<String>,
},
HexRepository {
repo: Option<String>,
url: String,
auth_key: Option<String>,
public_key_fingerprint: Option<String>,
},
MavenRepository {
url: String,
username: Option<String>,
password: Option<String>,
},
NpmRegistry {
url: String,
username: Option<String>,
password: Option<String>,
#[serde(default)]
replaces_base: bool,
},
NugetFeed {
url: String,
username: Option<String>,
password: Option<String>,
},
PythonIndex {
url: String,
username: Option<String>,
password: Option<String>,
#[serde(default)]
replaces_base: bool,
},
RubygemsServer {
url: String,
username: Option<String>,
password: Option<String>,
#[serde(default)]
replaces_base: bool,
},
TerraformRegistry {
url: String,
token: Option<String>,
},
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Cooldown {
pub default_days: Option<u64>,
pub semver_major_days: Option<u64>,
pub semver_minor_days: Option<u64>,
pub semver_patch_days: Option<u64>,
#[serde(default)]
pub include: Vec<String>,
#[serde(default)]
pub exclude: Vec<String>,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum Directories {
Directory(String),
Directories(Vec<String>),
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case", remote = "Self")]
pub struct Update {
#[serde(default)]
pub allow: Vec<Allow>,
#[serde(default)]
pub assignees: IndexSet<String>,
pub commit_message: Option<CommitMessage>,
pub cooldown: Option<Cooldown>,
#[serde(flatten)]
pub directories: Directories,
#[serde(default)]
pub groups: IndexMap<String, Group>,
#[serde(default)]
pub ignore: Vec<Ignore>,
#[serde(default)]
pub insecure_external_code_execution: AllowDeny,
#[serde(default = "default_labels")]
pub labels: IndexSet<String>,
pub milestone: Option<u64>,
#[serde(default = "default_open_pull_requests_limit")]
pub open_pull_requests_limit: u64,
pub package_ecosystem: PackageEcosystem,
#[serde(default)]
pub rebase_strategy: RebaseStrategy,
#[serde(default, deserialize_with = "crate::common::scalar_or_vector")]
pub registries: Vec<String>,
#[serde(default)]
pub reviewers: IndexSet<String>,
pub schedule: Option<Schedule>,
pub target_branch: Option<String>,
pub pull_request_branch_name: Option<PullRequestBranchName>,
#[serde(default)]
pub vendor: bool,
pub versioning_strategy: Option<VersioningStrategy>,
pub multi_ecosystem_group: Option<String>,
pub patterns: Option<IndexSet<String>>,
#[serde(default)]
pub exclude_paths: Option<IndexSet<String>>,
}
impl<'de> Deserialize<'de> for Update {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let update = Self::deserialize(deserializer)?;
if update.multi_ecosystem_group.is_some() && update.patterns.is_none() {
return Err(custom_error::<D>(
"`patterns` must be set when `multi-ecosystem-group` is set",
));
}
if update.multi_ecosystem_group.is_some() {
if update.milestone.is_some() {
return Err(custom_error::<D>(
"`milestone` may not be set when `multi-ecosystem-group` is set",
));
}
if update.target_branch.is_some() {
return Err(custom_error::<D>(
"`target-branch` may not be set when `multi-ecosystem-group` is set",
));
}
if update.commit_message.is_some() {
return Err(custom_error::<D>(
"`commit-message` may not be set when `multi-ecosystem-group` is set",
));
}
if update.pull_request_branch_name.is_some() {
return Err(custom_error::<D>(
"`pull-request-branch-name` may not be set when `multi-ecosystem-group` is set",
));
}
}
Ok(update)
}
}
#[inline]
fn default_labels() -> IndexSet<String> {
IndexSet::from(["dependencies".to_string()])
}
#[inline]
fn default_open_pull_requests_limit() -> u64 {
5
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Allow {
pub dependency_name: Option<String>,
pub dependency_type: Option<DependencyType>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum DependencyType {
Direct,
Indirect,
All,
Production,
Development,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct CommitMessage {
pub prefix: Option<String>,
pub prefix_development: Option<String>,
pub include: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Group {
pub dependency_type: Option<DependencyType>,
#[serde(default)]
pub patterns: IndexSet<String>,
#[serde(default)]
pub exclude_patterns: IndexSet<String>,
#[serde(default)]
pub update_types: IndexSet<UpdateType>,
}
#[derive(Deserialize, Debug, Hash, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum UpdateType {
Major,
Minor,
Patch,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Ignore {
pub dependency_name: Option<String>,
#[serde(default)]
pub update_types: IndexSet<String>,
#[serde(default)]
pub versions: IndexSet<String>,
}
#[derive(Deserialize, Debug, Default)]
#[serde(rename_all = "kebab-case")]
pub enum AllowDeny {
Allow,
#[default]
Deny,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum PackageEcosystem {
Bazel,
Bun,
Bundler,
Cargo,
Composer,
Conda,
Devcontainers,
Docker,
DockerCompose,
DotnetSdk,
Helm,
Julia,
Elm,
Gitsubmodule,
GithubActions,
Gomod,
Gradle,
Maven,
Mix,
Nix,
Npm,
Nuget,
Opentofu,
Pip,
PreCommit,
Pub,
RustToolchain,
Swift,
Terraform,
Uv,
Vcpkg,
}
#[derive(Deserialize, Debug, Default, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum RebaseStrategy {
#[default]
Auto,
Disabled,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case", remote = "Self")]
pub struct Schedule {
pub interval: Interval,
pub day: Option<Day>,
pub time: Option<String>,
pub timezone: Option<String>,
pub cronjob: Option<String>,
}
impl<'de> Deserialize<'de> for Schedule {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let schedule = Self::deserialize(deserializer)?;
if schedule.interval == Interval::Cron && schedule.cronjob.is_none() {
return Err(custom_error::<D>(
"`schedule.cronjob` must be set when `schedule.interval` is `cron`",
));
}
if schedule.interval != Interval::Cron && schedule.cronjob.is_some() {
return Err(custom_error::<D>(
"`schedule.cronjob` may only be set when `schedule.interval` is `cron`",
));
}
Ok(schedule)
}
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum Interval {
Daily,
Weekly,
Monthly,
Quarterly,
Semiannually,
Yearly,
Cron,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum Day {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct PullRequestBranchName {
pub separator: Option<String>,
}
#[derive(Deserialize, Debug, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum VersioningStrategy {
Auto,
Increase,
IncreaseIfNecessary,
LockfileOnly,
Widen,
}