1use std::any::Any;
16use std::fmt::{self, Display};
17
18use common_error::ext::ErrorExt;
19use common_error::status_code::StatusCode;
20use common_macro::stack_trace_debug;
21use serde::{Deserialize, Serialize};
22use snafu::{Snafu, ensure};
23use store_api::storage::ColumnId;
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub enum IndexTarget {
28 ColumnId(ColumnId),
29}
30
31impl Display for IndexTarget {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 IndexTarget::ColumnId(id) => write!(f, "{}", id),
35 }
36 }
37}
38
39impl IndexTarget {
40 pub fn decode(key: &str) -> Result<Self, TargetKeyError> {
42 validate_column_key(key)?;
43 let id = key
44 .parse::<ColumnId>()
45 .map_err(|_| InvalidColumnIdSnafu { value: key }.build())?;
46 Ok(IndexTarget::ColumnId(id))
47 }
48}
49
50#[derive(Snafu, Clone, PartialEq, Eq)]
52#[stack_trace_debug]
53pub enum TargetKeyError {
54 #[snafu(display("target key cannot be empty"))]
55 Empty,
56
57 #[snafu(display("target key must contain digits only: {key}"))]
58 InvalidCharacters { key: String },
59
60 #[snafu(display("failed to parse column id from '{value}'"))]
61 InvalidColumnId { value: String },
62}
63
64impl ErrorExt for TargetKeyError {
65 fn status_code(&self) -> StatusCode {
66 StatusCode::InvalidArguments
67 }
68
69 fn as_any(&self) -> &dyn Any {
70 self
71 }
72}
73
74fn validate_column_key(key: &str) -> Result<(), TargetKeyError> {
75 ensure!(!key.is_empty(), EmptySnafu);
76 ensure!(
77 key.chars().all(|ch| ch.is_ascii_digit()),
78 InvalidCharactersSnafu { key }
79 );
80 Ok(())
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn encode_decode_column() {
89 let target = IndexTarget::ColumnId(42);
90 let key = format!("{}", target);
91 assert_eq!(key, "42");
92 let decoded = IndexTarget::decode(&key).unwrap();
93 assert_eq!(decoded, target);
94 }
95
96 #[test]
97 fn decode_rejects_empty() {
98 let err = IndexTarget::decode("").unwrap_err();
99 assert!(matches!(err, TargetKeyError::Empty));
100 }
101
102 #[test]
103 fn decode_rejects_invalid_digits() {
104 let err = IndexTarget::decode("1a2").unwrap_err();
105 assert!(matches!(err, TargetKeyError::InvalidCharacters { .. }));
106 }
107}