1use anyhow::{anyhow, Context, Result};
2use clap::ValueEnum;
3use std::path::PathBuf;
4
5#[derive(ValueEnum, Clone, Copy, Debug, PartialEq)]
6pub enum OutputFormat {
7 Table,
8 Json,
9 Csv,
10 Parquet,
12}
13
14#[derive(ValueEnum, Clone, Debug)]
15pub enum ImportFormat {
16 Csv,
17 Json,
18 Parquet,
19}
20
21#[derive(ValueEnum, Clone, Debug)]
22pub enum ExportFormat {
23 Csv,
24 Json,
25 Parquet,
26 Cql,
27}
28
29#[derive(ValueEnum, Clone, Debug)]
30pub enum InfoOutputFormat {
31 Text,
32 Json,
33 Csv,
34}
35
36impl std::fmt::Display for OutputFormat {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 OutputFormat::Table => write!(f, "table"),
40 OutputFormat::Json => write!(f, "json"),
41 OutputFormat::Csv => write!(f, "csv"),
42 OutputFormat::Parquet => write!(f, "parquet"),
43 }
44 }
45}
46
47impl std::fmt::Display for ImportFormat {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self {
50 ImportFormat::Csv => write!(f, "csv"),
51 ImportFormat::Json => write!(f, "json"),
52 ImportFormat::Parquet => write!(f, "parquet"),
53 }
54 }
55}
56
57impl std::fmt::Display for ExportFormat {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 ExportFormat::Csv => write!(f, "csv"),
61 ExportFormat::Json => write!(f, "json"),
62 ExportFormat::Parquet => write!(f, "parquet"),
63 ExportFormat::Cql => write!(f, "cql"),
64 }
65 }
66}
67
68impl std::fmt::Display for InfoOutputFormat {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 match self {
71 InfoOutputFormat::Text => write!(f, "text"),
72 InfoOutputFormat::Json => write!(f, "json"),
73 InfoOutputFormat::Csv => write!(f, "csv"),
74 }
75 }
76}
77
78impl std::str::FromStr for OutputFormat {
79 type Err = String;
80
81 fn from_str(s: &str) -> Result<Self, Self::Err> {
82 match s.to_lowercase().as_str() {
83 "table" => Ok(OutputFormat::Table),
84 "json" => Ok(OutputFormat::Json),
85 "csv" => Ok(OutputFormat::Csv),
86 _ => Err(format!("Invalid output format: {}", s)),
87 }
88 }
89}
90
91#[derive(Debug, Clone, PartialEq)]
93#[allow(dead_code)]
94pub enum CassandraVersion {
95 V311,
96 V400,
97 V500,
98 Unknown(String),
99}
100
101#[allow(dead_code)]
102impl CassandraVersion {
103 pub fn from_string(version: &str) -> Self {
104 match version {
105 "3.11" | "3.11.0" => CassandraVersion::V311,
106 "4.0" | "4.0.0" => CassandraVersion::V400,
107 "5.0" | "5.0.0" => CassandraVersion::V500,
108 _ => CassandraVersion::Unknown(version.to_string()),
109 }
110 }
111
112 pub fn to_string(&self) -> String {
113 match self {
114 CassandraVersion::V311 => "3.11".to_string(),
115 CassandraVersion::V400 => "4.0".to_string(),
116 CassandraVersion::V500 => "5.0".to_string(),
117 CassandraVersion::Unknown(v) => v.clone(),
118 }
119 }
120
121 pub fn is_supported(&self) -> bool {
122 matches!(
123 self,
124 CassandraVersion::V311 | CassandraVersion::V400 | CassandraVersion::V500
125 )
126 }
127}
128
129#[allow(dead_code)]
131pub fn detect_sstable_version(sstable_path: &PathBuf) -> Result<String> {
132 use std::fs::File;
133 use std::io::{Read, Seek, SeekFrom};
134
135 let mut file = File::open(sstable_path)
136 .with_context(|| format!("Failed to open SSTable file: {}", sstable_path.display()))?;
137
138 let mut header = vec![0u8; 16];
140 file.read_exact(&mut header)
141 .with_context(|| "Failed to read SSTable header for version detection")?;
142
143 if header.starts_with(b"SSTA") {
146 file.seek(SeekFrom::Start(4))?;
148 let mut version_bytes = vec![0u8; 4];
149 file.read_exact(&mut version_bytes)?;
150
151 let version = u32::from_be_bytes([
152 version_bytes[0],
153 version_bytes[1],
154 version_bytes[2],
155 version_bytes[3],
156 ]);
157
158 match version {
159 0x0001 => Ok("3.11".to_string()),
160 0x0002 => Ok("4.0".to_string()),
161 0x0003 => Ok("5.0".to_string()),
162 _ => Ok("unknown".to_string()),
163 }
164 } else {
165 file.seek(SeekFrom::End(-512))?;
167 let mut footer = vec![0u8; 512];
168 file.read(&mut footer)?;
169
170 if footer.iter().any(|&b| b != 0) {
171 Ok("4.0".to_string())
173 } else {
174 Ok("3.11".to_string())
176 }
177 }
178}
179
180#[allow(dead_code)]
182pub fn validate_cassandra_version(version: &str) -> Result<CassandraVersion> {
183 let cassandra_version = CassandraVersion::from_string(version);
184
185 if !cassandra_version.is_supported() {
186 return Err(anyhow!(
187 "Unsupported Cassandra version: {}. Supported versions: 3.11, 4.0, 5.0",
188 version
189 ));
190 }
191
192 Ok(cassandra_version)
193}
194
195#[allow(dead_code)]
197pub fn create_version_error(
198 base_error: &str,
199 detected_version: Option<&str>,
200 provided_version: Option<&str>,
201) -> String {
202 let mut error_msg = base_error.to_string();
203
204 if let Some(detected) = detected_version {
205 error_msg.push_str(&format!("\nDetected SSTable version: {detected}"));
206 }
207
208 if let Some(provided) = provided_version {
209 error_msg.push_str(&format!("\nProvided Cassandra version: {provided}"));
210 }
211
212 error_msg.push_str("\nHint: Try using --auto-detect flag for automatic version detection");
213 error_msg
214 .push_str("\n or specify --cassandra-version to override (supported: 3.11, 4.0, 5.0)");
215
216 error_msg
217}