use crate::{impl_serde_from_str, sql::ResourceIdParser, Identifier, ParseError, ParseResult};
use std::{
fmt::{self, Display},
str::FromStr,
};
#[derive(Debug, PartialEq, Eq, Clone, Hash, Copy)]
pub struct ResourceId {
schema: Identifier,
object_name: Identifier,
}
impl ResourceId {
pub fn new(schema: Identifier, object_name: Identifier) -> Self {
Self {
schema,
object_name,
}
}
pub fn try_new(schema: &str, object_name: &str) -> ParseResult<Self> {
let schema = Identifier::try_new(schema)?;
let object_name = Identifier::try_new(object_name)?;
Ok(ResourceId {
schema,
object_name,
})
}
pub fn schema(&self) -> Identifier {
self.schema
}
pub fn object_name(&self) -> Identifier {
self.object_name
}
pub fn storage_format(&self) -> String {
let ResourceId {
schema,
object_name,
} = self;
let schema = schema.name().to_string().to_uppercase();
let object_name = object_name.name().to_string().to_uppercase();
format!("{schema}:{object_name}")
}
}
impl Display for ResourceId {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let ResourceId {
schema,
object_name,
} = self;
formatter.write_str(format!("{schema}.{object_name}").as_str())
}
}
impl FromStr for ResourceId {
type Err = ParseError;
fn from_str(string: &str) -> ParseResult<Self> {
let (schema, object_name) = ResourceIdParser::new()
.parse(string)
.map_err(|e| ParseError::ResourceIdParseError(format!("{:?}", e)))?;
Ok(ResourceId {
schema: Identifier::new(schema),
object_name: Identifier::new(object_name),
})
}
}
impl_serde_from_str!(ResourceId);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn try_new_resource_id() {
let resource_id =
ResourceId::try_new("G00d_identifier", "_can_start_with_underscore").unwrap();
assert_eq!(resource_id.schema().name(), "g00d_identifier");
assert_eq!(
resource_id.object_name().name(),
"_can_start_with_underscore"
);
}
#[test]
fn resource_id_from_str() {
let resource_id =
ResourceId::from_str("G00d_identifier._can_start_with_underscore").unwrap();
assert_eq!(resource_id.schema().name(), "g00d_identifier");
assert_eq!(resource_id.schema().name(), "g00d_identifier");
}
#[test]
fn try_new_resource_id_with_additional_characters_fails() {
assert!(ResourceId::try_new("GOOD_IDENTIFIER", "GOOD_IDENTIFIER.").is_err());
assert!(ResourceId::try_new("GOOD_IDENTIFIER.", "GOOD_IDENTIFIER").is_err());
assert!(ResourceId::try_new("BAD$IDENTIFIER", "GOOD_IDENTIFIER").is_err());
assert!(ResourceId::try_new("GOOD_IDENTIFIER", "BAD IDENTIFIER").is_err());
}
#[test]
fn we_can_parse_valid_resource_ids_with_white_spaces_at_beginning_or_end() {
let resource_id =
ResourceId::from_str(" GOOD_IDENTIFIER._can_start_with_underscore ").unwrap();
assert_eq!(resource_id.schema().name(), "good_identifier");
assert_eq!(
resource_id.object_name().name(),
"_can_start_with_underscore"
);
let resource_id = ResourceId::try_new(
" GOOD_IDENTIFIER ",
" _can_start_with_underscore ",
)
.unwrap();
assert_eq!(resource_id.schema().name(), "good_identifier");
assert_eq!(
resource_id.object_name().name(),
"_can_start_with_underscore"
);
}
#[test]
fn display_resource_id() {
assert_eq!(
ResourceId::try_new("GOOD_IDENTIFIER", "good_identifier")
.unwrap()
.to_string(),
"good_identifier.good_identifier"
);
assert_eq!(
ResourceId::try_new("g00d_identifier", "_can_Start_with_underscore")
.unwrap()
.to_string(),
"g00d_identifier._can_start_with_underscore"
);
}
#[test]
fn resource_id_storage_format() {
assert_eq!(
ResourceId::try_new("GOOD_IDENTIFIER", "good_identifier")
.unwrap()
.storage_format(),
"GOOD_IDENTIFIER:GOOD_IDENTIFIER"
);
assert_eq!(
ResourceId::try_new("g00d_identifier", "_can_Start_with_underscore")
.unwrap()
.storage_format(),
"G00D_IDENTIFIER:_CAN_START_WITH_UNDERSCORE"
);
}
#[test]
fn invalid_resource_id_parsing_fails() {
assert!(ResourceId::from_str("GOOD_IDENTIFIER").is_err());
assert!(ResourceId::from_str("GOOD_IDENTIFIER:GOOD_IDENTIFIER").is_err());
assert!(ResourceId::from_str("BAD$IDENTIFIER.GOOD_IDENTIFIER").is_err());
assert!(ResourceId::from_str("GOOD_IDENTIFIER.BAD_IDENT!FIER").is_err());
assert!(ResourceId::from_str("GOOD_IDENTIFIER.BAD IDENTIFIER").is_err());
assert!(ResourceId::from_str("GOOD_IDENTIFIER.13AD_IDENTIFIER").is_err());
assert!(ResourceId::from_str("13AD_IDENTIFIER.GOOD_IDENTIFIER").is_err());
assert!(ResourceId::from_str("GOOD_IDENTIFIER.").is_err());
assert!(ResourceId::from_str(".GOOD_IDENTIFIER").is_err());
assert!(ResourceId::from_str(".").is_err());
assert!(ResourceId::from_str("GOOD_IDENTIFIER").is_err());
assert!(ResourceId::from_str("GOOD_IDENTIFIER.GOOD_IDENTIFIER.GOOD_IDENTIFIER").is_err());
}
#[test]
fn resource_id_serializes_to_string() {
let resource_id = ResourceId::try_new("GOOD_IDENTIFIER", "good_identifier").unwrap();
let serialized = serde_json::to_string(&resource_id).unwrap();
assert_eq!(serialized, r#""good_identifier.good_identifier""#);
}
#[test]
fn resource_id_deserializes_from_string() {
let resource_id = ResourceId::try_new("GOOD_IDENTIFIER", "good_identifier").unwrap();
let deserialized: ResourceId =
serde_json::from_str(r#""good_identifier.good_identifier""#).unwrap();
assert_eq!(resource_id, deserialized);
}
#[test]
fn resource_id_fails_to_deserialize_with_invalid_identifier() {
let deserialized: Result<ResourceId, _> =
serde_json::from_str(r#""good_identifier.bad!identifier"#);
assert!(deserialized.is_err());
}
}