1use std::path::{Path, PathBuf};
3
4use clap::{Parser, Subcommand, ValueHint};
5
6use crate::version;
7
8#[derive(Parser)]
10#[command(version = version())]
11#[command(propagate_version = true)]
12pub(crate) struct Cli {
13 #[command(subcommand)]
14 pub(crate) cmd: Commands,
15}
16
17#[derive(Subcommand)]
18pub(crate) enum Commands {
19 Run {
20 #[arg(long, short, value_hint = ValueHint::FilePath, default_value = "agglayer.toml", env = "CONFIG_PATH")]
22 cfg: PathBuf,
23 },
24
25 Config {
26 #[arg(
28 long,
29 short,
30 value_hint = ValueHint::DirPath,
31 env = "CONFIG_PATH"
32 )]
33 base_dir: PathBuf,
34 },
35
36 ValidateConfig {
37 #[arg(
39 long,
40 short,
41 value_hint = ValueHint::FilePath,
42 )]
43 path: PathBuf,
44 },
45
46 Vkey,
47 VkeySelector,
48
49 #[clap(subcommand)]
50 Backup(Backup),
51}
52
53#[derive(Subcommand)]
54pub(crate) enum Backup {
55 List {
57 #[arg(long, short, value_hint = ValueHint::FilePath, default_value = "agglayer.toml", env = "CONFIG_PATH")]
58 config_path: PathBuf,
59 },
60
61 Restore {
63 #[arg(long, short, value_hint = ValueHint::FilePath, default_value = "agglayer.toml", env = "CONFIG_PATH")]
64 config_path: PathBuf,
65 #[arg(value_parser = parse_db_kind_version)]
66 db_versions: Vec<(DbKind, u32)>,
67 },
68}
69
70#[derive(Debug, Clone)]
71pub(crate) enum DbKind {
72 State,
73 Pending,
74 Epoch(u64),
75}
76
77impl DbKind {
78 pub(crate) fn create_paths(
79 &self,
80 cfg: &agglayer_config::Config,
81 path: &Path,
82 ) -> (PathBuf, PathBuf) {
83 match self {
84 Self::State => (cfg.storage.state_db_path.clone(), path.join("state")),
85 Self::Pending => (cfg.storage.pending_db_path.clone(), path.join("pending")),
86 Self::Epoch(epoch_number) => (
87 cfg.storage.epochs_db_path.join(format!("{epoch_number}")),
88 path.join(format!("epochs/{epoch_number}")),
89 ),
90 }
91 }
92}
93
94impl std::str::FromStr for DbKind {
95 type Err = String;
96
97 fn from_str(s: &str) -> Result<Self, Self::Err> {
98 match s.to_lowercase().as_str().trim() {
99 "state" => Ok(DbKind::State),
100 "pending" => Ok(DbKind::Pending),
101 s => {
102 let Some(epoch) = s.strip_prefix("epoch_") else {
103 return Err(format!("Unexpected DbKind: {s}"));
104 };
105
106 let epoch = epoch
107 .parse::<u64>()
108 .map_err(|e| format!("Invalid epoch: {e}"))?;
109
110 Ok(DbKind::Epoch(epoch))
111 }
112 }
113 }
114}
115
116fn parse_db_kind_version(s: &str) -> Result<(DbKind, u32), String> {
117 let parts: Vec<&str> = s.split(':').collect();
118 if parts.len() != 2 {
119 return Err(format!(
120 "Invalid format for argument '{s}'. Expected 'name:version'"
121 ));
122 }
123
124 let db_kind = parts[0].parse::<DbKind>()?;
125 let version = parts[1]
126 .parse::<u32>()
127 .map_err(|e| format!("Invalid version '{}': {}", parts[1], e))?;
128
129 Ok((db_kind, version))
130}
131
132#[cfg(test)]
133mod tests {
134 use agglayer_config::Config;
135
136 use super::*;
137
138 #[test]
139 fn testing_path_state() {
140 let path_normal = PathBuf::from("/tmp/normal");
141 let config = Config::new(&path_normal);
142
143 let path_normal = path_normal.join("storage");
144 let kind = DbKind::State;
145 let path_backup = PathBuf::from("/tmp/storage/backup");
146 let (destination, backup) = kind.create_paths(&config, &path_backup);
147
148 assert_eq!(destination, path_normal.join("state"));
149 assert_eq!(backup, path_backup.join("state"));
150 }
151
152 #[test]
153 fn testing_path_pending() {
154 let path_normal = PathBuf::from("/tmp/normal");
155 let config = Config::new(&path_normal);
156
157 let path_normal = path_normal.join("storage");
158 let kind = DbKind::Pending;
159 let path_backup = PathBuf::from("/tmp/storage/backup");
160 let (destination, backup) = kind.create_paths(&config, &path_backup);
161
162 assert_eq!(destination, path_normal.join("pending"));
163 assert_eq!(backup, path_backup.join("pending"));
164 }
165
166 #[test]
167 fn testing_path_epochs() {
168 let path_normal = PathBuf::from("/tmp/normal");
169 let config = Config::new(&path_normal);
170
171 let path_normal = path_normal.join("storage");
172 let kind = DbKind::Epoch(10);
173 let path_backup = PathBuf::from("/tmp/storage/backup");
174 let (destination, backup) = kind.create_paths(&config, &path_backup);
175
176 assert_eq!(destination, path_normal.join("epochs/10"));
177 assert_eq!(backup, path_backup.join("epochs/10"));
178 }
179}