use std::fmt;
use std::time::SystemTime;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use crate::time::{parse_iso8601_utc, serde_iso8601};
use crate::types::License;
use crate::version::parse_app_version;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReleaseAccess {
#[serde(serialize_with = "serde_iso8601::option::serialize", skip_serializing_if = "Option::is_none")]
pub updates_until: Option<SystemTime>,
#[serde(skip_serializing_if = "Option::is_none")]
pub up_to_major: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub up_to_version: Option<String>,
}
impl ReleaseAccess {
pub fn covers(&self, version: &str) -> bool {
self.covers_at(version, SystemTime::now())
}
pub fn covers_at(&self, version: &str, now: SystemTime) -> bool {
if self.updates_until.is_none() && self.up_to_major.is_none() {
return true;
}
let Some(app_version) = parse_app_version(version) else {
return true;
};
if self.up_to_major.is_some_and(|major| app_version.major <= major) {
return true;
}
let Some(until) = self.updates_until else {
return false;
};
if now <= until {
return true;
}
match self.up_to_version.as_deref().and_then(parse_app_version) {
Some(last_covered) => app_version <= last_covered,
None => true,
}
}
pub(crate) fn from_stored(value: &Value) -> Self {
Self {
updates_until: value
.get("updatesUntil")
.and_then(Value::as_str)
.and_then(|text| parse_iso8601_utc(text).ok()),
up_to_major: value.get("upToMajor").and_then(Value::as_u64),
up_to_version: value.get("upToVersion").and_then(Value::as_str).map(str::to_owned),
}
}
}
impl<'de> Deserialize<'de> for ReleaseAccess {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(Self::from_stored(&Value::deserialize(deserializer)?))
}
}
pub(crate) fn lenient_release_access<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<ReleaseAccess>, D::Error> {
let value = Value::deserialize(deserializer)?;
Ok(value.is_object().then(|| ReleaseAccess::from_stored(&value)))
}
#[derive(Debug, Clone, PartialEq)]
pub struct ReleaseNotCovered {
message: String,
license: Option<License>,
app_version: String,
release_access: Option<ReleaseAccess>,
upgrade_url: Option<String>,
}
impl ReleaseNotCovered {
pub(crate) fn new(
message: String,
license: Option<License>,
app_version: String,
release_access: Option<ReleaseAccess>,
upgrade_url: Option<String>,
) -> Self {
let release_access =
release_access.or_else(|| license.as_ref().and_then(|license| license.release_access.clone()));
Self { message, license, app_version, release_access, upgrade_url }
}
pub fn message(&self) -> &str {
&self.message
}
pub fn license(&self) -> Option<&License> {
self.license.as_ref()
}
pub fn app_version(&self) -> &str {
&self.app_version
}
pub fn release_access(&self) -> Option<&ReleaseAccess> {
self.release_access.as_ref()
}
pub fn upgrade_url(&self) -> Option<&str> {
self.upgrade_url.as_deref()
}
}
impl fmt::Display for ReleaseNotCovered {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}