agglayer/
cli.rs

1//! Agglayer command line interface.
2use std::path::{Path, PathBuf};
3
4use clap::{Parser, Subcommand, ValueHint};
5
6use crate::version;
7
8/// Agglayer command line interface.
9#[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        /// The path to the configuration file.
21        #[arg(long, short, value_hint = ValueHint::FilePath, default_value = "agglayer.toml", env = "CONFIG_PATH")]
22        cfg: PathBuf,
23    },
24
25    Config {
26        /// The path to the agglayer dir.
27        #[arg(
28            long,
29            short,
30            value_hint = ValueHint::DirPath,
31            env = "CONFIG_PATH"
32        )]
33        base_dir: PathBuf,
34    },
35
36    ValidateConfig {
37        /// The path to the configuration file.
38        #[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 all backups.
56    List {
57        #[arg(long, short, value_hint = ValueHint::FilePath, default_value = "agglayer.toml", env = "CONFIG_PATH")]
58        config_path: PathBuf,
59    },
60
61    /// Restore from a backup.
62    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}