use std::{
collections::HashMap,
fmt, str,
time::{SystemTime, UNIX_EPOCH},
};
use derive_builder::Builder;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};
use crate::error::Error;
use _serde::SnapshotEnum;
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Builder, Getters)]
#[serde(from = "SnapshotEnum", into = "SnapshotEnum")]
#[builder(build_fn(error = "Error"), setter(prefix = "with"))]
pub struct Snapshot {
#[builder(default = "generate_snapshot_id()")]
snapshot_id: i64,
#[builder(setter(strip_option), default)]
parent_snapshot_id: Option<i64>,
sequence_number: i64,
#[builder(
default = "SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64"
)]
timestamp_ms: i64,
manifest_list: String,
#[builder(default)]
summary: Summary,
#[builder(setter(strip_option), default)]
schema_id: Option<i32>,
}
pub fn generate_snapshot_id() -> i64 {
let mut bytes: [u8; 8] = [0u8; 8];
getrandom::fill(&mut bytes).unwrap();
i64::from_le_bytes(bytes).abs()
}
impl fmt::Display for Snapshot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
&serde_json::to_string(self).map_err(|_| fmt::Error)?,
)
}
}
impl str::FromStr for Snapshot {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s).map_err(Error::from)
}
}
pub(crate) mod _serde {
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::{Operation, Snapshot, Summary};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub(super) enum SnapshotEnum {
V2(SnapshotV2),
V1(SnapshotV1),
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct SnapshotV2 {
pub snapshot_id: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_snapshot_id: Option<i64>,
pub sequence_number: i64,
pub timestamp_ms: i64,
pub manifest_list: String,
pub summary: Summary,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema_id: Option<i32>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct SnapshotV1 {
pub snapshot_id: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_snapshot_id: Option<i64>,
pub timestamp_ms: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub manifest_list: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manifests: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<Summary>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema_id: Option<i32>,
}
impl From<SnapshotEnum> for Snapshot {
fn from(value: SnapshotEnum) -> Self {
match value {
SnapshotEnum::V2(value) => value.into(),
SnapshotEnum::V1(value) => value.into(),
}
}
}
impl From<Snapshot> for SnapshotEnum {
fn from(value: Snapshot) -> Self {
SnapshotEnum::V2(value.into())
}
}
impl From<SnapshotV1> for Snapshot {
fn from(v1: SnapshotV1) -> Self {
Snapshot {
snapshot_id: v1.snapshot_id,
parent_snapshot_id: v1.parent_snapshot_id,
sequence_number: 0,
timestamp_ms: v1.timestamp_ms,
manifest_list: v1.manifest_list.unwrap_or_default(),
summary: v1.summary.unwrap_or(Summary {
operation: Operation::default(),
other: HashMap::new(),
}),
schema_id: v1.schema_id,
}
}
}
impl From<Snapshot> for SnapshotV1 {
fn from(v1: Snapshot) -> Self {
SnapshotV1 {
snapshot_id: v1.snapshot_id,
parent_snapshot_id: v1.parent_snapshot_id,
timestamp_ms: v1.timestamp_ms,
manifest_list: Some(v1.manifest_list),
summary: Some(v1.summary),
schema_id: v1.schema_id,
manifests: None,
}
}
}
impl From<SnapshotV2> for Snapshot {
fn from(value: SnapshotV2) -> Self {
Snapshot {
snapshot_id: value.snapshot_id,
parent_snapshot_id: value.parent_snapshot_id,
sequence_number: value.sequence_number,
timestamp_ms: value.timestamp_ms,
manifest_list: value.manifest_list,
summary: value.summary,
schema_id: value.schema_id,
}
}
}
impl From<Snapshot> for SnapshotV2 {
fn from(value: Snapshot) -> Self {
SnapshotV2 {
snapshot_id: value.snapshot_id,
parent_snapshot_id: value.parent_snapshot_id,
sequence_number: value.sequence_number,
timestamp_ms: value.timestamp_ms,
manifest_list: value.manifest_list,
summary: value.summary,
schema_id: value.schema_id,
}
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum Operation {
#[default]
Append,
Replace,
Overwrite,
Delete,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Default)]
pub struct Summary {
pub operation: Operation,
#[serde(flatten)]
pub other: HashMap<String, String>,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct SnapshotReference {
pub snapshot_id: i64,
#[serde(flatten)]
pub retention: SnapshotRetention,
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
#[serde(rename_all = "lowercase", tag = "type")]
pub enum SnapshotRetention {
#[serde(rename_all = "kebab-case")]
Branch {
#[serde(skip_serializing_if = "Option::is_none")]
min_snapshots_to_keep: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
max_snapshot_age_ms: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
max_ref_age_ms: Option<i64>,
},
#[serde(rename_all = "kebab-case")]
Tag {
max_ref_age_ms: i64,
},
}
impl Default for SnapshotRetention {
fn default() -> Self {
SnapshotRetention::Branch {
max_ref_age_ms: None,
max_snapshot_age_ms: None,
min_snapshots_to_keep: None,
}
}
}