Skip to main content

reduct_base/msg/
lifecycle_api.rs

1// Copyright 2021-2026 ReductSoftware UG
2// Licensed under the Apache License, Version 2.0
3
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8/// Lifecycle policy action type.
9#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Default)]
10#[serde(rename_all = "lowercase")]
11pub enum LifecycleType {
12    /// Delete records matched by lifecycle settings.
13    #[default]
14    Delete,
15    /// Compress blocks matched by lifecycle settings.
16    Compress,
17}
18
19/// Lifecycle mode.
20#[repr(u8)]
21#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
22#[serde(rename_all = "lowercase")]
23pub enum LifecycleMode {
24    /// Lifecycle is active and executes actions.
25    Enabled,
26    /// Lifecycle is inactive and does not execute actions.
27    Disabled,
28    /// Lifecycle runs in preview mode and does not remove records.
29    #[serde(rename = "dry_run")]
30    DryRun,
31}
32
33impl Default for LifecycleMode {
34    fn default() -> Self {
35        Self::Enabled
36    }
37}
38
39/// Lifecycle policy settings.
40#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
41pub struct LifecycleSettings {
42    /// Lifecycle policy action type.
43    #[serde(rename = "type")]
44    pub lifecycle_type: LifecycleType,
45    /// Bucket to apply the lifecycle policy to.
46    pub bucket: String,
47    /// Entries to clean. If empty, all removable entries are matched. Prefix wildcards are supported.
48    /// System metadata entries (e.g. `entry/$meta`) are always excluded.
49    #[serde(default)]
50    pub entries: Vec<String>,
51    /// Records older than this duration, e.g. "30d", "24h", or "3600s".
52    pub older_than: String,
53    /// Interval between lifecycle runs, e.g. "30m", "1h", or "3600s".
54    #[serde(default = "default_lifecycle_interval")]
55    pub interval: String,
56    /// When condition.
57    #[serde(default)]
58    pub when: Option<Value>,
59    /// Lifecycle mode.
60    #[serde(default)]
61    pub mode: LifecycleMode,
62}
63
64impl Default for LifecycleSettings {
65    fn default() -> Self {
66        Self {
67            lifecycle_type: LifecycleType::default(),
68            bucket: String::default(),
69            entries: Vec::default(),
70            older_than: String::default(),
71            interval: default_lifecycle_interval(),
72            when: Option::default(),
73            mode: LifecycleMode::default(),
74        }
75    }
76}
77
78fn default_lifecycle_interval() -> String {
79    "3600s".to_string()
80}
81
82/// Lifecycle policy information.
83#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
84pub struct LifecycleInfo {
85    /// Lifecycle policy name.
86    pub name: String,
87    /// Lifecycle policy is provisioned.
88    pub is_provisioned: bool,
89    /// Lifecycle worker is running.
90    pub is_running: bool,
91    /// Lifecycle policy action type.
92    #[serde(default, rename = "type")]
93    pub lifecycle_type: LifecycleType,
94    /// Lifecycle mode.
95    #[serde(default)]
96    pub mode: LifecycleMode,
97    /// Last lifecycle run timestamp.
98    #[serde(default)]
99    pub last_run: Option<DateTime<Utc>>,
100}
101
102/// Payload for updating lifecycle mode.
103#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
104pub struct LifecycleModePayload {
105    pub mode: LifecycleMode,
106}
107
108/// Lifecycle policy list.
109#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
110pub struct LifecycleList {
111    /// Lifecycle policies.
112    pub lifecycles: Vec<LifecycleInfo>,
113}
114
115/// Full lifecycle policy information.
116#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
117pub struct FullLifecycleInfo {
118    /// Info.
119    pub info: LifecycleInfo,
120    /// Settings.
121    pub settings: LifecycleSettings,
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn lifecycle_mode_default_is_enabled() {
130        assert_eq!(LifecycleMode::default(), LifecycleMode::Enabled);
131    }
132
133    #[test]
134    fn lifecycle_mode_serde_roundtrip() {
135        assert_eq!(
136            serde_json::to_string(&LifecycleMode::Enabled).unwrap(),
137            "\"enabled\""
138        );
139        assert_eq!(
140            serde_json::to_string(&LifecycleMode::Disabled).unwrap(),
141            "\"disabled\""
142        );
143        assert_eq!(
144            serde_json::to_string(&LifecycleMode::DryRun).unwrap(),
145            "\"dry_run\""
146        );
147
148        assert_eq!(
149            serde_json::from_str::<LifecycleMode>("\"enabled\"").unwrap(),
150            LifecycleMode::Enabled
151        );
152        assert_eq!(
153            serde_json::from_str::<LifecycleMode>("\"disabled\"").unwrap(),
154            LifecycleMode::Disabled
155        );
156        assert_eq!(
157            serde_json::from_str::<LifecycleMode>("\"dry_run\"").unwrap(),
158            LifecycleMode::DryRun
159        );
160    }
161
162    #[test]
163    fn lifecycle_settings_mode_defaults_on_missing() {
164        let settings: LifecycleSettings = serde_json::from_str(
165            r#"{
166                "type": "delete",
167                "bucket": "bucket-1",
168                "entries": ["entry-1"],
169                "older_than": "1d",
170                "interval": "1h"
171            }"#,
172        )
173        .unwrap();
174
175        assert_eq!(settings.mode, LifecycleMode::Enabled);
176    }
177
178    #[test]
179    fn lifecycle_settings_type_is_required() {
180        let err = serde_json::from_str::<LifecycleSettings>(
181            r#"{
182                "bucket": "bucket-1",
183                "older_than": "1d"
184            }"#,
185        )
186        .unwrap_err();
187
188        assert!(err.to_string().contains("missing field `type`"));
189    }
190
191    #[test]
192    fn lifecycle_settings_default_interval_is_3600s() {
193        let settings = LifecycleSettings::default();
194        assert_eq!(settings.interval, "3600s");
195    }
196
197    #[test]
198    fn lifecycle_info_mode_defaults_on_missing() {
199        let info: LifecycleInfo = serde_json::from_str(
200            r#"{
201                "name": "test",
202                "is_provisioned": false,
203                "is_running": true
204            }"#,
205        )
206        .unwrap();
207
208        assert_eq!(info.lifecycle_type, LifecycleType::Delete);
209        assert_eq!(info.mode, LifecycleMode::Enabled);
210        assert_eq!(info.last_run, None);
211    }
212
213    #[test]
214    fn lifecycle_mode_payload_serde_roundtrip() {
215        let payload = LifecycleModePayload {
216            mode: LifecycleMode::Disabled,
217        };
218
219        let serialized = serde_json::to_string(&payload).unwrap();
220        assert_eq!(serialized, r#"{"mode":"disabled"}"#);
221
222        let deserialized: LifecycleModePayload = serde_json::from_str(&serialized).unwrap();
223        assert_eq!(deserialized, payload);
224    }
225}