-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
use std::{fmt::Display, str::FromStr}; | ||
|
||
use lazy_static::lazy_static; | ||
use regex::Regex; | ||
use snafu::{ensure, Snafu}; | ||
|
||
const LABEL_KEY_PREFIX_MAX_LEN: usize = 253; | ||
const LABEL_KEY_NAME_MAX_LEN: usize = 63; | ||
|
||
lazy_static! { | ||
static ref LABEL_KEY_PREFIX_REGEX: Regex = | ||
Regex::new(r"^[a-zA-Z](\.?[a-zA-Z0-9-])*\.[a-zA-Z]{2,}\.?$").unwrap(); | ||
static ref LABEL_KEY_NAME_REGEX: Regex = | ||
Regex::new(r"^[a-z0-9A-Z]([a-z0-9A-Z-_.]*[a-z0-9A-Z]+)?$").unwrap(); | ||
} | ||
|
||
#[derive(Debug, Snafu)] | ||
pub enum LabelKeyError {} | ||
|
||
pub struct LabelKey { | ||
prefix: Option<LabelKeyPrefix>, | ||
name: LabelKeyName, | ||
} | ||
|
||
impl FromStr for LabelKey { | ||
type Err = LabelKeyError; | ||
|
||
fn from_str(input: &str) -> Result<Self, Self::Err> { | ||
Check failure on line 28 in rust/stackable-cockpit/src/platform/label/key.rs GitHub Actions / clippy[clippy] rust/stackable-cockpit/src/platform/label/key.rs#L28
Raw output
Check failure on line 28 in rust/stackable-cockpit/src/platform/label/key.rs GitHub Actions / clippy[clippy] rust/stackable-cockpit/src/platform/label/key.rs#L28
Raw output
|
||
todo!() | ||
} | ||
} | ||
|
||
impl Display for LabelKey { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match &self.prefix { | ||
Some(prefix) => write!(f, "{}/{}", prefix, self.name), | ||
None => write!(f, "{}", self.name), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Snafu)] | ||
pub enum LabelKeyPrefixError { | ||
#[snafu(display("prefix segment of key cannot be empty"))] | ||
PrefixEmpty, | ||
|
||
#[snafu(display("prefix segment of key exceeds the maximum length - expected 253 characters or less, got {length}"))] | ||
PrefixTooLong { length: usize }, | ||
|
||
#[snafu(display("prefix segment of key contains non-ascii characters"))] | ||
PrefixNotAscii, | ||
|
||
#[snafu(display("prefix segment of key violates kubernetes format"))] | ||
PrefixInvalid, | ||
} | ||
|
||
pub struct LabelKeyPrefix(String); | ||
|
||
impl Display for LabelKeyPrefix { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", self.0) | ||
} | ||
} | ||
|
||
impl FromStr for LabelKeyPrefix { | ||
type Err = LabelKeyPrefixError; | ||
|
||
fn from_str(input: &str) -> Result<Self, Self::Err> { | ||
// The prefix cannot be empty when one is provided | ||
ensure!(!input.is_empty(), PrefixEmptySnafu); | ||
|
||
// The length of the prefix cannot exceed 253 characters | ||
ensure!( | ||
input.len() <= LABEL_KEY_PREFIX_MAX_LEN, | ||
PrefixTooLongSnafu { | ||
length: input.len() | ||
} | ||
); | ||
|
||
// The prefix cannot contain non-ascii characters | ||
ensure!(input.is_ascii(), PrefixNotAsciiSnafu); | ||
|
||
// The prefix must use the format specified by Kubernetes | ||
ensure!(LABEL_KEY_PREFIX_REGEX.is_match(input), PrefixInvalidSnafu); | ||
|
||
Ok(Self(input.to_string())) | ||
} | ||
} | ||
|
||
#[derive(Debug, Snafu)] | ||
pub enum LabelKeyNameError { | ||
#[snafu(display("name segment of key cannot be empty"))] | ||
NameEmpty, | ||
|
||
#[snafu(display("name segment of key exceeds the maximum length - expected 63 characters or less, got {length}"))] | ||
NameTooLong { length: usize }, | ||
|
||
#[snafu(display("name segment of key contains non-ascii characters"))] | ||
NameNotAscii, | ||
|
||
#[snafu(display("name segment of key violates kubernetes format"))] | ||
NameInvalid, | ||
} | ||
|
||
pub struct LabelKeyName(String); | ||
|
||
impl Display for LabelKeyName { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", self.0) | ||
} | ||
} | ||
|
||
impl FromStr for LabelKeyName { | ||
type Err = LabelKeyNameError; | ||
|
||
fn from_str(input: &str) -> Result<Self, Self::Err> { | ||
// The name cannot be empty | ||
ensure!(!input.is_empty(), NameEmptySnafu); | ||
|
||
// The length of the name cannot exceed 63 characters | ||
ensure!( | ||
input.len() <= LABEL_KEY_NAME_MAX_LEN, | ||
NameTooLongSnafu { | ||
length: input.len() | ||
} | ||
); | ||
|
||
// The name cannot contain non-ascii characters | ||
ensure!(input.is_ascii(), NameNotAsciiSnafu); | ||
|
||
// The name must use the format specified by Kubernetes | ||
ensure!(LABEL_KEY_NAME_REGEX.is_match(input), NameInvalidSnafu); | ||
|
||
Ok(Self(input.to_string())) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
use std::{fmt::Display, str::FromStr}; | ||
|
||
use snafu::{ensure, ResultExt, Snafu}; | ||
|
||
mod key; | ||
mod value; | ||
|
||
pub use key::*; | ||
pub use value::*; | ||
|
||
#[derive(Debug, Snafu)] | ||
Check failure on line 11 in rust/stackable-cockpit/src/platform/label/mod.rs GitHub Actions / clippy[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L11
Raw output
Check failure on line 11 in rust/stackable-cockpit/src/platform/label/mod.rs GitHub Actions / clippy[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L11
Raw output
|
||
pub enum LabelParseError { | ||
#[snafu(display("label input cannot be empty"))] | ||
EmptyInput, | ||
|
||
#[snafu(display("invalid number of equal signs - expected exactly 1, got {signs}"))] | ||
InvalidEqualSignCount { signs: usize }, | ||
|
||
#[snafu(display("failed to parse label key"))] | ||
LabelKeyError { source: LabelKeyError }, | ||
|
||
#[snafu(display("failed to parse label value"))] | ||
LabelValueError { source: LabelValueError }, | ||
} | ||
|
||
// NOTE (Techassi): This should probably be upstreamed into operator-rs | ||
pub struct Label { | ||
key: LabelKey, | ||
value: LabelValue, | ||
} | ||
|
||
impl FromStr for Label { | ||
type Err = LabelParseError; | ||
|
||
fn from_str(input: &str) -> Result<Self, Self::Err> { | ||
let input = input.trim(); | ||
|
||
// Ensure the input is not empty | ||
ensure!(!input.is_empty(), EmptyInputSnafu); | ||
|
||
// Then split up the key and value, which is separated by an equal | ||
// sign | ||
let parts: Vec<_> = input.split('=').collect(); | ||
|
||
// Ensure there are only two parts | ||
ensure!( | ||
parts.len() == 2, | ||
InvalidEqualSignCountSnafu { signs: parts.len() } | ||
); | ||
|
||
// Parse key and value parts | ||
let key = LabelKey::from_str(parts[0]).context(LabelKeySnafu)?; | ||
let value = LabelValue::new(parts[1]).context(LabelValueSnafu)?; | ||
|
||
Ok(Self { key, value }) | ||
} | ||
} | ||
|
||
impl Display for Label { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}={}", self.key, self.value) | ||
} | ||
} | ||
|
||
impl Label { | ||
pub fn new(key: LabelKey, value: LabelValue) -> Self { | ||
Self { key, value } | ||
} | ||
} | ||
|
||
macro_rules! label { | ||
Check failure on line 71 in rust/stackable-cockpit/src/platform/label/mod.rs GitHub Actions / clippy[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L71
Raw output
Check failure on line 71 in rust/stackable-cockpit/src/platform/label/mod.rs GitHub Actions / clippy[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L71
Raw output
|
||
($Input:expr) => { | ||
$crate::platform::label::Label::from_str($Input) | ||
}; | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
Check failure on line 79 in rust/stackable-cockpit/src/platform/label/mod.rs GitHub Actions / clippy[clippy] rust/stackable-cockpit/src/platform/label/mod.rs#L79
Raw output
|
||
|
||
#[test] | ||
fn test_basic() { | ||
// let l = label!("stackable.tech/vendor=Stackable").unwrap(); | ||
|
||
// let key_prefix = LabelKeyPrefix::new("stackable.tech").unwrap(); | ||
// let key = LabelKey::new(Some(key_prefix), "vendor").unwrap(); | ||
// let value = LabelValue::new("Stackable").unwrap(); | ||
|
||
// let label = Label::new(key, value); | ||
// assert_eq!(label.to_string(), "stackable.tech/vendor=Stackable") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
use std::fmt::Display; | ||
|
||
use lazy_static::lazy_static; | ||
use regex::Regex; | ||
use snafu::{ensure, Snafu}; | ||
|
||
const LABEL_VALUE_MAX_LEN: usize = 63; | ||
|
||
lazy_static! { | ||
static ref LABEL_VALUE_REGEX: Regex = | ||
Regex::new(r"^[a-z0-9A-Z]([a-z0-9A-Z-_.]*[a-z0-9A-Z]+)?$").unwrap(); | ||
} | ||
|
||
#[derive(Debug, Snafu)] | ||
pub enum LabelValueError { | ||
#[snafu(display( | ||
"value exceeds the maximum length - expected 63 characters or less, got {length}" | ||
))] | ||
ValueTooLong { length: usize }, | ||
|
||
#[snafu(display("value contains non-ascii characters"))] | ||
ValueNotAscii, | ||
|
||
#[snafu(display("value violates kubernetes format"))] | ||
ValueInvalid, | ||
} | ||
|
||
pub struct LabelValue(String); | ||
|
||
impl Display for LabelValue { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
write!(f, "{}", self.0) | ||
} | ||
} | ||
|
||
impl LabelValue { | ||
pub fn new(value: impl Into<String>) -> Result<Self, LabelValueError> { | ||
let value: String = value.into(); | ||
|
||
// The length of the value cannot exceed 63 characters, but can be | ||
// empty | ||
ensure!( | ||
value.len() <= LABEL_VALUE_MAX_LEN, | ||
ValueTooLongSnafu { | ||
length: value.len() | ||
} | ||
); | ||
|
||
// The value cannot contain non-ascii characters | ||
ensure!(value.is_ascii(), ValueNotAsciiSnafu); | ||
|
||
// The value must use the format specified by Kubernetes | ||
ensure!(LABEL_VALUE_REGEX.is_match(&value), ValueInvalidSnafu); | ||
|
||
Ok(Self(value)) | ||
} | ||
} |