Skip to content

Commit 22c5b98

Browse files
feat: Extend the compile-time checks for name types
1 parent 44830bb commit 22c5b98

4 files changed

Lines changed: 148 additions & 82 deletions

File tree

rust/operator-binary/src/framework.rs

Lines changed: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,8 @@
2020
//! become less frequent, then this module can be incorporated into stackable-operator. The module
2121
//! structure should already resemble the one of stackable-operator.
2222
23-
use std::{fmt::Display, str::FromStr};
24-
25-
use snafu::{ResultExt, Snafu, ensure};
26-
use stackable_operator::kvp::LabelValue;
23+
use snafu::Snafu;
2724
use strum::{EnumDiscriminants, IntoStaticStr};
28-
use uuid::Uuid;
2925

3026
pub mod builder;
3127
pub mod cluster_resources;
@@ -107,13 +103,16 @@ pub trait NameIsValidLabelValue {
107103
}
108104

109105
/// Restricted string type with attributes like maximum length.
106+
///
107+
/// Fully-qualified types are used to ease the import into other modules.
108+
#[macro_export(local_inner_macros)]
110109
macro_rules! attributed_string_type {
111110
($name:ident, $description:literal, $example:literal $(, $attribute:tt)*) => {
112-
#[doc = concat!($description, ", e.g. \"", $example, "\"")]
111+
#[doc = std::concat!($description, ", e.g. \"", $example, "\"")]
113112
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
114113
pub struct $name(String);
115114

116-
impl Display for $name {
115+
impl std::fmt::Display for $name {
117116
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118117
self.0.fmt(f)
119118
}
@@ -137,14 +136,17 @@ macro_rules! attributed_string_type {
137136
}
138137
}
139138

140-
impl FromStr for $name {
141-
type Err = Error;
139+
impl std::str::FromStr for $name {
140+
type Err = $crate::framework::Error;
142141

143142
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
143+
// ResultExt::context is used on most but not all usages of this macro
144+
#[allow(unused_imports)]
145+
use snafu::ResultExt;
144146

145-
ensure!(
147+
snafu::ensure!(
146148
!s.is_empty(),
147-
EmptyStringSnafu {}
149+
$crate::framework::EmptyStringSnafu {}
148150
);
149151

150152
$(attributed_string_type!(@from_str $name, s, $attribute);)*
@@ -157,7 +159,7 @@ macro_rules! attributed_string_type {
157159
impl $name {
158160
#[allow(dead_code)]
159161
pub fn from_str_unsafe(s: &str) -> Self {
160-
FromStr::from_str(s).expect("should be a valid {name}")
162+
std::str::FromStr::from_str(s).expect("should be a valid {name}")
161163
}
162164

163165
// A dead_code warning is emitted if there is no unit test that calls this function.
@@ -170,56 +172,72 @@ macro_rules! attributed_string_type {
170172
};
171173
(@from_str $name:ident, $s:expr, (max_length = $max_length:expr)) => {
172174
let length = $s.len() as usize;
173-
ensure!(
175+
snafu::ensure!(
174176
length <= $name::MAX_LENGTH,
175-
LengthExceededSnafu {
177+
$crate::framework::LengthExceededSnafu {
176178
length,
177179
max_length: $name::MAX_LENGTH,
178180
}
179181
);
180182
};
181183
(@from_str $name:ident, $s:expr, is_rfc_1123_dns_subdomain_name) => {
182-
stackable_operator::validation::is_rfc_1123_subdomain($s).context(InvalidRfc1123DnsSubdomainNameSnafu)?;
184+
stackable_operator::validation::is_rfc_1123_subdomain($s).context($crate::framework::InvalidRfc1123DnsSubdomainNameSnafu)?;
183185
};
184186
(@from_str $name:ident, $s:expr, is_rfc_1123_label_name) => {
185-
stackable_operator::validation::is_rfc_1123_label($s).context(InvalidRfc1123LabelNameSnafu)?;
187+
stackable_operator::validation::is_rfc_1123_label($s).context($crate::framework::InvalidRfc1123LabelNameSnafu)?;
186188
};
187189
(@from_str $name:ident, $s:expr, is_rfc_1035_label_name) => {
188-
stackable_operator::validation::is_rfc_1035_label($s).context(InvalidRfc1035LabelNameSnafu)?;
190+
stackable_operator::validation::is_rfc_1035_label($s).context($crate::framework::InvalidRfc1035LabelNameSnafu)?;
189191
};
190192
(@from_str $name:ident, $s:expr, is_valid_label_value) => {
191-
LabelValue::from_str($s).context(InvalidLabelValueSnafu)?;
193+
stackable_operator::kvp::LabelValue::from_str($s).context($crate::framework::InvalidLabelValueSnafu)?;
192194
};
193195
(@from_str $name:ident, $s:expr, is_uid) => {
194-
Uuid::try_parse($s).context(InvalidUidSnafu)?;
196+
uuid::Uuid::try_parse($s).context($crate::framework::InvalidUidSnafu)?;
195197
};
196198
(@trait_impl $name:ident, (max_length = $max_length:expr)) => {
197199
impl $name {
198200
// type arithmetic would be better
199201
pub const MAX_LENGTH: usize = $max_length;
200202
}
201203
};
204+
(@trait_impl $name:ident, is_rfc_1035_label_name) => {
205+
impl $name {
206+
pub const IS_RFC_1035_LABEL_NAME: bool = true;
207+
pub const IS_RFC_1123_LABEL_NAME: bool = true;
208+
pub const IS_RFC_1123_SUBDOMAIN_NAME: bool = true;
209+
}
210+
};
202211
(@trait_impl $name:ident, is_rfc_1123_dns_subdomain_name) => {
212+
impl $name {
213+
pub const IS_RFC_1123_SUBDOMAIN_NAME: bool = true;
214+
}
203215
};
204216
(@trait_impl $name:ident, is_rfc_1123_label_name) => {
205-
};
206-
(@trait_impl $name:ident, is_rfc_1035_label_name) => {
217+
impl $name {
218+
pub const IS_RFC_1123_LABEL_NAME: bool = true;
219+
pub const IS_RFC_1123_SUBDOMAIN_NAME: bool = true;
220+
}
207221
};
208222
(@trait_impl $name:ident, is_uid) => {
209-
impl From<Uuid> for $name {
210-
fn from(value: Uuid) -> Self {
223+
impl From<uuid::Uuid> for $name {
224+
fn from(value: uuid::Uuid) -> Self {
211225
Self(value.to_string())
212226
}
213227
}
214228

215-
impl From<&Uuid> for $name {
216-
fn from(value: &Uuid) -> Self {
229+
impl From<&uuid::Uuid> for $name {
230+
fn from(value: &uuid::Uuid) -> Self {
217231
Self(value.to_string())
218232
}
219233
}
220234
};
221235
(@trait_impl $name:ident, is_valid_label_value) => {
222-
impl NameIsValidLabelValue for $name {
236+
impl $name {
237+
pub const IS_VALID_LABEL_VALUE: bool = true;
238+
}
239+
240+
impl $crate::framework::NameIsValidLabelValue for $name {
223241
fn to_label_value(&self) -> String {
224242
self.0.clone()
225243
}
@@ -321,7 +339,12 @@ attributed_string_type! {
321339
StatefulSetName,
322340
"The name of a StatefulSet",
323341
"opensearch-nodes-default",
324-
(max_length = min(MAX_RFC_1123_LABEL_NAME_LENGTH, MAX_LABEL_VALUE_LENGTH)),
342+
(max_length = min(
343+
// see https://github.com/kubernetes/kubernetes/issues/64023
344+
MAX_RFC_1123_LABEL_NAME_LENGTH
345+
- 1 /* dash */
346+
- 10 /* digits for the controller-revision-hash label */,
347+
MAX_LABEL_VALUE_LENGTH)),
325348
is_rfc_1123_label_name,
326349
is_valid_label_value
327350
}
@@ -351,6 +374,7 @@ attributed_string_type! {
351374
// A suffix is added to produce a label value. An according compile-time check ensures that
352375
// max_length cannot be set higher.
353376
(max_length = min(54, MAX_LABEL_VALUE_LENGTH)),
377+
is_rfc_1123_dns_subdomain_name,
354378
is_valid_label_value
355379
}
356380
attributed_string_type! {
@@ -364,9 +388,10 @@ attributed_string_type! {
364388
ClusterName,
365389
"The name of a cluster/stacklet",
366390
"my-opensearch-cluster",
367-
// Suffixes are added to produce a resource names. According compile-time check ensures that
391+
// Suffixes are added to produce resource names. According compile-time checks ensure that
368392
// max_length cannot be set higher.
369393
(max_length = min(24, MAX_LABEL_VALUE_LENGTH)),
394+
is_rfc_1035_label_name,
370395
is_valid_label_value
371396
}
372397
attributed_string_type! {
@@ -391,6 +416,7 @@ attributed_string_type! {
391416
// are valid, max_length is restricted. Compile-time checks ensure that max_length cannot be
392417
// set higher if not other names like the RoleName are set lower accordingly.
393418
(max_length = min(16, MAX_LABEL_VALUE_LENGTH)),
419+
is_rfc_1123_label_name,
394420
is_valid_label_value
395421
}
396422
attributed_string_type! {
@@ -401,23 +427,20 @@ attributed_string_type! {
401427
// valid, max_length is restricted. Compile-time checks ensure that max_length cannot be set
402428
// higher if not other names like the RoleGroupName are set lower accordingly.
403429
(max_length = min(10, MAX_LABEL_VALUE_LENGTH)),
430+
is_rfc_1123_label_name,
404431
is_valid_label_value
405432
}
406433

407434
#[cfg(test)]
408435
mod tests {
409-
use std::{fmt::Display, str::FromStr};
436+
use std::str::FromStr;
410437

411-
use snafu::{ResultExt, ensure};
412-
use uuid::{Uuid, uuid};
438+
use uuid::uuid;
413439

414440
use super::{
415-
ClusterName, ClusterRoleName, ConfigMapName, ControllerName, EmptyStringSnafu, Error,
416-
ErrorDiscriminants, InvalidLabelValueSnafu, InvalidRfc1035LabelNameSnafu,
417-
InvalidRfc1123DnsSubdomainNameSnafu, InvalidRfc1123LabelNameSnafu, InvalidUidSnafu,
418-
LabelValue, LengthExceededSnafu, NamespaceName, OperatorName, PersistentVolumeClaimName,
419-
ProductVersion, RoleBindingName, RoleGroupName, RoleName, ServiceAccountName, ServiceName,
420-
StatefulSetName, Uid, VolumeName,
441+
ClusterName, ClusterRoleName, ConfigMapName, ControllerName, ErrorDiscriminants,
442+
NamespaceName, OperatorName, PersistentVolumeClaimName, ProductVersion, RoleBindingName,
443+
RoleGroupName, RoleName, ServiceAccountName, ServiceName, StatefulSetName, Uid, VolumeName,
421444
};
422445
use crate::framework::{NameIsValidLabelValue, ProductName};
423446

@@ -505,6 +528,10 @@ mod tests {
505528
fn test_attributed_string_type_is_rfc_1035_label_name() {
506529
type T = IsRfc1035LabelNameTest;
507530

531+
let _ = T::IS_RFC_1035_LABEL_NAME;
532+
let _ = T::IS_RFC_1123_LABEL_NAME;
533+
let _ = T::IS_RFC_1123_SUBDOMAIN_NAME;
534+
508535
T::test_example();
509536
assert_eq!(
510537
Err(ErrorDiscriminants::InvalidRfc1035LabelName),
@@ -523,6 +550,8 @@ mod tests {
523550
fn test_attributed_string_type_is_rfc_1123_dns_subdomain_name() {
524551
type T = IsRfc1123DnsSubdomainNameTest;
525552

553+
let _ = T::IS_RFC_1123_SUBDOMAIN_NAME;
554+
526555
T::test_example();
527556
assert_eq!(
528557
Err(ErrorDiscriminants::InvalidRfc1123DnsSubdomainName),
@@ -541,6 +570,9 @@ mod tests {
541570
fn test_attributed_string_type_is_rfc_1123_label_name() {
542571
type T = IsRfc1123LabelNameTest;
543572

573+
let _ = T::IS_RFC_1123_LABEL_NAME;
574+
let _ = T::IS_RFC_1123_SUBDOMAIN_NAME;
575+
544576
T::test_example();
545577
assert_eq!(
546578
Err(ErrorDiscriminants::InvalidRfc1123LabelName),
@@ -559,6 +591,8 @@ mod tests {
559591
fn test_attributed_string_type_is_valid_label_value() {
560592
type T = IsValidLabelValueTest;
561593

594+
let _ = T::IS_VALID_LABEL_VALUE;
595+
562596
T::test_example();
563597
assert_eq!(
564598
Err(ErrorDiscriminants::InvalidLabelValue),

rust/operator-binary/src/framework/cluster_resources.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub fn cluster_resources_new(
1616
cluster_uid: &Uid,
1717
apply_strategy: ClusterResourceApplyStrategy,
1818
) -> ClusterResources {
19-
// Compile-time check
19+
// compile-time check
2020
// ClusterResources::new creates a label value from the given app name by appending
2121
// `-operator`. For the resulting label value to be valid, it must not exceed
2222
// MAX_LABEL_VALUE_LENGTH.

0 commit comments

Comments
 (0)