index/
target.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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/// Describes an index target. Column ids are the only supported variant for now.
26#[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    /// Parse a target key string back into an index target description.
41    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/// Errors that can occur when working with index target keys.
51#[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}