#[derive(PartialEq, Eq, Clone, Debug)]
pub struct EntityUri {
value: String,
type_start: usize,
version_start: usize,
name_start: usize,
}
impl PartialOrd for EntityUri {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for EntityUri {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.value.cmp(&other.value)
}
}
impl std::hash::Hash for EntityUri {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl EntityUri {
pub fn as_str(&self) -> &str {
&self.value
}
#[allow(clippy::inherent_to_string_shadow_display)]
pub fn to_string(&self) -> String {
self.value.clone()
}
pub fn into_string(self) -> String {
self.value
}
pub fn kind(&self) -> &str {
&self.value[..self.name_start - 1]
}
pub fn namespace(&self) -> &str {
&self.value[..self.type_start - 1]
}
pub fn entity_type(&self) -> &str {
&self.value[self.type_start..self.version_start - 2]
}
pub fn version(&self) -> &str {
&self.value[self.version_start..self.name_start - 1]
}
pub fn name(&self) -> &str {
&self.value[self.name_start..]
}
pub fn new_kind_name(kind: &str, name: &str) -> Result<Self, EntityUriParseError> {
Self::parse(format!("{}:{}", kind, name))
}
pub fn parse(s: impl Into<String>) -> Result<Self, EntityUriParseError> {
let s = s.into();
let (kind, name) = s.split_once(':').ok_or_else(|| {
EntityUriParseError::new(s.clone(), EntityUriParseErrorKind::MissingKindNameSeparator)
})?;
let (ns, kind) = kind.split_once('/').ok_or_else(|| {
EntityUriParseError::new(
s.clone(),
EntityUriParseErrorKind::MissingKindNamespaceSeparator,
)
})?;
let (ty, version) = kind.split_once('.').ok_or_else(|| {
EntityUriParseError::new(s.clone(), EntityUriParseErrorKind::InvalidType)
})?;
if !is_valid_namespace(ns) {
return Err(EntityUriParseError::new(
s,
EntityUriParseErrorKind::InvalidNamespace,
));
}
if !is_valid_type_name(ty) {
return Err(EntityUriParseError::new(
s,
EntityUriParseErrorKind::InvalidType,
));
}
if !is_valid_version(version) {
return Err(EntityUriParseError::new(
s,
EntityUriParseErrorKind::InvalidTypeVersion,
));
}
if !is_valid_name(name) {
return Err(EntityUriParseError::new(
s,
EntityUriParseErrorKind::InvalidName,
));
}
let type_start = ns.len() + 1;
let version_start = type_start + ty.len() + 2;
let name_start = version_start + version.len();
Ok(Self {
value: s,
type_start,
version_start,
name_start,
})
}
}
impl std::fmt::Display for EntityUri {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
fn is_valid_namespace(s: &str) -> bool {
s.split('.').all(|p| {
!p.is_empty()
&& p.chars().all(|c| match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' => true,
_ => false,
})
})
}
fn is_valid_type_name(s: &str) -> bool {
!s.is_empty()
&& s.chars().all(|c| match c {
'a'..='z' | 'A'..='Z' | '0'..='9' => true,
_ => false,
})
}
fn is_valid_version(s: &str) -> bool {
!s.is_empty()
&& s.chars().all(|c| match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => true,
_ => false,
})
}
fn is_valid_name(name: &str) -> bool {
!name.is_empty()
&& name.chars().all(|c| match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '+' => true,
_ => false,
})
}
impl std::str::FromStr for EntityUri {
type Err = EntityUriParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s.to_string())
}
}
impl serde::Serialize for EntityUri {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.value)
}
}
impl<'de> serde::Deserialize<'de> for EntityUri {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let value = String::deserialize(deserializer)?;
Self::parse(value).map_err(serde::de::Error::custom)
}
}
impl schemars::JsonSchema for EntityUri {
fn schema_name() -> String {
"EntityUri".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
schemars::schema::Schema::Object(schemars::schema::SchemaObject {
instance_type: Some(schemars::schema::InstanceType::String.into()),
..Default::default()
})
}
}
#[derive(Clone, Debug)]
pub struct EntityUriParseError {
value: String,
kind: EntityUriParseErrorKind,
}
impl EntityUriParseError {
pub fn new(value: impl Into<String>, kind: EntityUriParseErrorKind) -> Self {
Self {
value: value.into(),
kind,
}
}
}
impl std::fmt::Display for EntityUriParseError {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(_f, "invalid entity URI: '{}' ({:?})", self.value, self.kind)
}
}
impl std::error::Error for EntityUriParseError {}
#[derive(Clone, Debug)]
pub enum EntityUriParseErrorKind {
MissingKindNameSeparator,
MissingKindNamespaceSeparator,
InvalidNamespace,
InvalidType,
InvalidName,
InvalidTypeVersion,
}
impl std::fmt::Display for EntityUriParseErrorKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EntityUriParseErrorKind::MissingKindNameSeparator => {
write!(f, "missing kind/name separator")
}
EntityUriParseErrorKind::MissingKindNamespaceSeparator => {
write!(f, "missing kind/namespace separator")
}
EntityUriParseErrorKind::InvalidNamespace => write!(f, "invalid namespace"),
EntityUriParseErrorKind::InvalidType => write!(f, "invalid type"),
EntityUriParseErrorKind::InvalidName => write!(f, "invalid name"),
EntityUriParseErrorKind::InvalidTypeVersion => write!(f, "invalid type version"),
}
}
}
#[derive(
serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq, Eq, Clone, Debug,
)]
pub enum EntityOrRef<T> {
#[serde(rename = "ref")]
Ref(EntityUri),
#[serde(rename = "item")]
Item(T),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entity_uri_parse() {
let u = EntityUri::parse("my-ns.com/Ent.v1:lala123".to_string()).unwrap();
assert_eq!(u.namespace(), "my-ns.com");
assert_eq!(u.entity_type(), "Ent");
assert_eq!(u.version(), "1");
assert_eq!(u.name(), "lala123");
}
}