Validate label names
This commit is contained in:
122
crates/pile-config/src/misc.rs
Normal file
122
crates/pile-config/src/misc.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use core::slice;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::Deref;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smartstring::{LazyCompact, SmartString};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum OneOrMany<T: Debug + Clone> {
|
||||
One(T),
|
||||
Many(Vec<T>),
|
||||
}
|
||||
|
||||
impl<T: Debug + Clone> OneOrMany<T> {
|
||||
pub fn to_vec(self) -> Vec<T> {
|
||||
match self {
|
||||
Self::One(x) => vec![x],
|
||||
Self::Many(x) => x,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
match self {
|
||||
Self::One(x) => slice::from_ref(&x),
|
||||
Self::Many(x) => &x[..],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// MARK: Label
|
||||
//
|
||||
|
||||
/// A sanitized [String], guaranteed to only contain
|
||||
/// chars in `A-z`, `0-9`, and `-_`.
|
||||
///
|
||||
/// Used for names of datasets, fields, etc.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
pub struct Label(SmartString<LazyCompact>);
|
||||
|
||||
impl Label {
|
||||
pub const VALID_CHARS: &str =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
|
||||
|
||||
pub fn new(str: impl Into<String>) -> Option<Self> {
|
||||
let str: String = str.into();
|
||||
for c in str.chars() {
|
||||
if !Self::VALID_CHARS.contains(c) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
return Some(Self(str.into()));
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn into_string(self) -> String {
|
||||
self.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Label {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Label> for String {
|
||||
fn from(value: Label) -> Self {
|
||||
value.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Label {
|
||||
type Error = InvalidLabel;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
Self::new(value).ok_or(InvalidLabel)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Label {
|
||||
type Error = InvalidLabel;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Self::new(value).ok_or(InvalidLabel)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Label {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Label {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct InvalidLabel;
|
||||
|
||||
impl Display for InvalidLabel {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Invalid label: must only contain characters in {}",
|
||||
Label::VALID_CHARS
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidLabel {}
|
||||
Reference in New Issue
Block a user