use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::{
ContentSource, ExtraFileSpec, StringOrBool, TemplatedExtraFile, deserialize_string_or_bool_opt,
};
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default)]
pub struct ReleaseConfig {
pub github: Option<ScmRepoConfig>,
pub gitlab: Option<ScmRepoConfig>,
pub gitea: Option<ScmRepoConfig>,
pub draft: Option<bool>,
#[schemars(schema_with = "prerelease_schema")]
pub prerelease: Option<PrereleaseConfig>,
#[schemars(schema_with = "make_latest_schema")]
pub make_latest: Option<MakeLatestConfig>,
pub name_template: Option<String>,
pub header: Option<ContentSource>,
pub footer: Option<ContentSource>,
pub extra_files: Option<Vec<ExtraFileSpec>>,
pub templated_extra_files: Option<Vec<TemplatedExtraFile>>,
#[serde(deserialize_with = "deserialize_string_or_bool_opt", default)]
pub skip_upload: Option<StringOrBool>,
pub replace_existing_draft: Option<bool>,
pub replace_existing_artifacts: Option<bool>,
#[serde(
default,
alias = "disable",
deserialize_with = "deserialize_string_or_bool_opt"
)]
pub skip: Option<StringOrBool>,
pub mode: Option<String>,
pub ids: Option<Vec<String>>,
pub target_commitish: Option<String>,
pub discussion_category_name: Option<String>,
pub include_meta: Option<bool>,
pub use_existing_draft: Option<bool>,
pub tag: Option<String>,
}
impl ReleaseConfig {
pub const DEFAULT_NAME_TEMPLATE: &'static str = "{{ Tag }}";
pub const DEFAULT_MODE: &'static str = "keep-existing";
pub const VALID_MODES: &[&'static str] = &["keep-existing", "append", "prepend", "replace"];
pub fn resolved_name_template(&self) -> &str {
self.name_template
.as_deref()
.unwrap_or(Self::DEFAULT_NAME_TEMPLATE)
}
pub fn resolved_mode(&self) -> anyhow::Result<&str> {
match self.mode.as_deref() {
None | Some("") => Ok(Self::DEFAULT_MODE),
Some(m) if Self::VALID_MODES.contains(&m) => Ok(m),
Some(other) => Err(anyhow::anyhow!(
"release: invalid mode '{}', must be one of: {}",
other,
Self::VALID_MODES.join(", ")
)),
}
}
pub fn resolved_draft(&self) -> bool {
self.draft.unwrap_or(false)
}
pub fn resolved_replace_existing_draft(&self) -> bool {
self.replace_existing_draft.unwrap_or(false)
}
pub fn resolved_replace_existing_artifacts(&self) -> bool {
self.replace_existing_artifacts.unwrap_or(false)
}
pub fn resolved_include_meta(&self) -> bool {
self.include_meta.unwrap_or(false)
}
pub fn resolved_use_existing_draft(&self) -> bool {
self.use_existing_draft.unwrap_or(false)
}
}
fn prerelease_schema(
_generator: &mut schemars::r#gen::SchemaGenerator,
) -> schemars::schema::Schema {
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation};
Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(vec![
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
enum_values: Some(vec![serde_json::json!("auto")]),
..Default::default()
}),
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))),
..Default::default()
}),
]),
..Default::default()
})),
..Default::default()
})
}
fn make_latest_schema(
_generator: &mut schemars::r#gen::SchemaGenerator,
) -> schemars::schema::Schema {
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation};
Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(vec![
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
enum_values: Some(vec![serde_json::json!("auto")]),
..Default::default()
}),
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))),
..Default::default()
}),
]),
..Default::default()
})),
..Default::default()
})
}
pub(super) fn skip_push_schema(
_generator: &mut schemars::r#gen::SchemaGenerator,
) -> schemars::schema::Schema {
use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation};
Schema::Object(SchemaObject {
subschemas: Some(Box::new(SubschemaValidation {
one_of: Some(vec![
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
enum_values: Some(vec![serde_json::json!("auto")]),
..Default::default()
}),
Schema::Object(SchemaObject {
instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Boolean))),
..Default::default()
}),
]),
..Default::default()
})),
..Default::default()
})
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ScmRepoConfig {
pub owner: String,
pub name: String,
}
pub type GitHubConfig = ScmRepoConfig;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ForceTokenKind {
GitHub,
GitLab,
Gitea,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct GitHubUrlsConfig {
pub api: Option<String>,
pub upload: Option<String>,
pub download: Option<String>,
pub skip_tls_verify: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct GitLabUrlsConfig {
pub api: Option<String>,
pub download: Option<String>,
pub skip_tls_verify: Option<bool>,
pub use_package_registry: Option<bool>,
pub use_job_token: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct GiteaUrlsConfig {
pub api: Option<String>,
pub download: Option<String>,
pub skip_tls_verify: Option<bool>,
}
macro_rules! impl_auto_or_bool_serde {
($ty:ty, $auto:path, $bool_variant:path) => {
impl Serialize for $ty {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
match self {
$auto => serializer.serialize_str("auto"),
$bool_variant(b) => serializer.serialize_bool(*b),
}
}
}
impl<'de> Deserialize<'de> for $ty {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = $ty;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"auto\" or a boolean")
}
fn visit_bool<E: serde::de::Error>(
self,
v: bool,
) -> std::result::Result<$ty, E> {
Ok($bool_variant(v))
}
fn visit_str<E: serde::de::Error>(
self,
v: &str,
) -> std::result::Result<$ty, E> {
if v == "auto" {
Ok($auto)
} else {
Err(E::custom(format!("expected \"auto\", got \"{}\"", v)))
}
}
}
deserializer.deserialize_any(Visitor)
}
}
};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrereleaseConfig {
Auto,
Bool(bool),
}
impl_auto_or_bool_serde!(
PrereleaseConfig,
PrereleaseConfig::Auto,
PrereleaseConfig::Bool
);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MakeLatestConfig {
Auto,
Bool(bool),
String(String),
}
impl Serialize for MakeLatestConfig {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
match self {
MakeLatestConfig::Auto => serializer.serialize_str("auto"),
MakeLatestConfig::Bool(b) => serializer.serialize_bool(*b),
MakeLatestConfig::String(s) => serializer.serialize_str(s),
}
}
}
impl<'de> Deserialize<'de> for MakeLatestConfig {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = MakeLatestConfig;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"auto\", a boolean, or a template string")
}
fn visit_bool<E: serde::de::Error>(
self,
v: bool,
) -> std::result::Result<MakeLatestConfig, E> {
Ok(MakeLatestConfig::Bool(v))
}
fn visit_str<E: serde::de::Error>(
self,
v: &str,
) -> std::result::Result<MakeLatestConfig, E> {
match v {
"auto" => Ok(MakeLatestConfig::Auto),
"true" => Ok(MakeLatestConfig::Bool(true)),
"false" => Ok(MakeLatestConfig::Bool(false)),
other => Ok(MakeLatestConfig::String(other.to_string())),
}
}
}
deserializer.deserialize_any(Visitor)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SkipPushConfig {
Auto,
Bool(bool),
Template(String),
}
impl Serialize for SkipPushConfig {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
match self {
SkipPushConfig::Auto => serializer.serialize_str("auto"),
SkipPushConfig::Bool(b) => serializer.serialize_bool(*b),
SkipPushConfig::Template(s) => serializer.serialize_str(s),
}
}
}
impl<'de> Deserialize<'de> for SkipPushConfig {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = SkipPushConfig;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"auto\", a boolean, or a template string")
}
fn visit_bool<E: serde::de::Error>(
self,
v: bool,
) -> std::result::Result<SkipPushConfig, E> {
Ok(SkipPushConfig::Bool(v))
}
fn visit_str<E: serde::de::Error>(
self,
v: &str,
) -> std::result::Result<SkipPushConfig, E> {
match v {
"auto" => Ok(SkipPushConfig::Auto),
"true" => Ok(SkipPushConfig::Bool(true)),
"false" => Ok(SkipPushConfig::Bool(false)),
other => Ok(SkipPushConfig::Template(other.to_string())),
}
}
}
deserializer.deserialize_any(Visitor)
}
}