proof_of_sql_parser/
identifier.rsuse crate::{sql::IdentifierParser, ParseError, ParseResult};
use alloc::{format, string::ToString};
use arrayvec::ArrayString;
use core::{cmp::Ordering, fmt, ops::Deref, str::FromStr};
#[derive(Debug, PartialEq, Eq, Clone, Hash, Ord, PartialOrd, Copy)]
pub struct Identifier {
name: ArrayString<64>,
}
impl Identifier {
pub(crate) fn new<S: AsRef<str>>(string: S) -> Self {
Self {
name: ArrayString::from(&string.as_ref().to_lowercase()).expect("Identifier too long"),
}
}
pub fn try_new<S: AsRef<str>>(string: S) -> ParseResult<Self> {
Self::from_str(string.as_ref())
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn as_str(&self) -> &str {
self.name()
}
}
impl FromStr for Identifier {
type Err = ParseError;
fn from_str(string: &str) -> ParseResult<Self> {
let name = IdentifierParser::new()
.parse(string)
.map_err(|e| ParseError::IdentifierParseError(
format!("failed to parse identifier, (you may have used a reserved keyword as an ID, i.e. 'timestamp') {:?}", e)))?;
Ok(Identifier::new(name))
}
}
crate::impl_serde_from_str!(Identifier);
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
self.name.fmt(f)
}
}
impl PartialEq<str> for Identifier {
fn eq(&self, other: &str) -> bool {
other.eq_ignore_ascii_case(&self.name)
}
}
impl PartialOrd<str> for Identifier {
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
self.name.partial_cmp(other.to_lowercase().as_str())
}
}
impl Deref for Identifier {
type Target = str;
fn deref(&self) -> &Self::Target {
self.name.as_str()
}
}
impl AsRef<str> for Identifier {
fn as_ref(&self) -> &str {
self.name.as_str()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::{borrow::ToOwned, vec, vec::Vec};
#[test]
fn from_str_identifier() {
assert_eq!(
Identifier::from_str("GOOD_IDENTIFIER13AD_IDENTIFIER")
.unwrap()
.name(),
"good_identifier13ad_identifier"
);
}
#[test]
fn from_str_identifier_with_additional_characters_fails() {
assert!(Identifier::from_str("GOOD_IDENTIFIER.").is_err());
assert!(Identifier::from_str("BAD$IDENTIFIER").is_err());
assert!(Identifier::from_str("BAD IDENTIFIER").is_err());
}
#[test]
fn identifier_from_str() {
assert_eq!(
Identifier::from_str("G00d_identifier").unwrap().name(),
"g00d_identifier"
);
}
#[test]
fn display_identifier() {
assert_eq!(
Identifier::from_str("GOOD_IDENTIFIER").unwrap().to_string(),
"good_identifier"
);
assert_eq!(
Identifier::from_str("_can_Start_with_underscore")
.unwrap()
.to_string(),
"_can_start_with_underscore"
);
}
#[test]
fn we_can_parse_valid_identifiers_with_white_spaces_at_beginning_or_end() {
assert_eq!(
Identifier::from_str(" GOOD_IDENTIFIER").unwrap().name(),
"good_identifier"
);
assert_eq!(
Identifier::from_str("GOOD_IDENTIFIER ").unwrap().name(),
"good_identifier"
);
}
#[test]
fn we_cannot_parse_invalid_identifiers() {
assert!(Identifier::from_str("").is_err());
assert!(Identifier::from_str(".").is_err());
assert!(Identifier::from_str("GOOD_IDENTIFIER:GOOD_IDENTIFIER").is_err());
assert!(Identifier::from_str("BAD$IDENTIFIER").is_err());
assert!(Identifier::from_str("BAD_IDENT!FIER").is_err());
assert!(Identifier::from_str("BAD IDENTIFIER").is_err());
assert!(Identifier::from_str("13AD_IDENTIFIER").is_err());
assert!(Identifier::from_str("$AD_IDENTIFIER").is_err());
assert!(Identifier::from_str("GOOD_IDENTIFIER.").is_err());
assert!(Identifier::from_str(".GOOD_IDENTIFIER").is_err());
assert!(Identifier::from_str(&"LONG_IDENTIFIER_OVER_64_CHARACTERS".repeat(12)).is_err());
let keywords = [
"all",
"asc",
"desc",
"as",
"and",
"from",
"not",
"or",
"select",
"where",
"order",
"by",
"limit",
"offset",
"group",
"min",
"max",
"count",
"sum",
"true",
"false",
"timestamp",
"to_timestamp",
];
for keyword in keywords.iter() {
assert!(
Identifier::from_str(keyword).is_err(),
"Should not parse keyword as identifier: {}",
keyword
);
}
}
#[test]
fn serialize_works() {
let identifier = Identifier::from_str("GOOD_IDENTIFIER").unwrap();
let serialized = serde_json::to_string(&identifier).unwrap();
assert_eq!(serialized, r#""good_identifier""#);
}
#[test]
fn deserialize_works() {
let identifier = Identifier::from_str("GOOD_IDENTIFIER").unwrap();
let deserialized: Identifier = serde_json::from_str(r#""good_identifier""#).unwrap();
assert_eq!(identifier, deserialized);
}
#[test]
fn deserialize_fails_on_invalid_identifier() {
let deserialized: Result<Identifier, _> = serde_json::from_str(r#""BAD IDENTIFIER""#);
assert!(deserialized.is_err());
}
#[test]
fn deserialize_fails_on_empty_string() {
let deserialized: Result<Identifier, _> = serde_json::from_str(r#""""#);
assert!(deserialized.is_err());
}
#[test]
fn deserialize_fails_on_long_identifier() {
let deserialized: Result<Identifier, _> = serde_json::from_str(&format!(
r#""{}""#,
"LONG_IDENTIFIER_OVER_64_CHARACTERS".repeat(12)
));
assert!(deserialized.is_err());
}
#[test]
fn deserialize_works_in_a_type_parameter() {
let deserialized: Vec<Identifier> =
serde_json::from_str(r#"[ "good_identifier" ]"#).unwrap();
assert_eq!(
deserialized,
vec![Identifier::from_str("GOOD_IDENTIFIER").unwrap()]
);
}
#[test]
fn strings_are_lower_case_when_converted_to_names() {
let raw_str = "sxt";
let string = "sXt".to_owned();
let lower_case = Identifier::new(raw_str);
let upper_case = Identifier::new("SXT");
let mixed_case = Identifier::new(string);
assert_eq!(lower_case, upper_case);
assert_eq!(lower_case, mixed_case);
assert_eq!(lower_case.name(), "sxt");
}
#[test]
#[should_panic]
fn long_names_panic() {
Identifier::new("t".repeat(65));
}
#[test]
#[should_panic]
fn long_unicode_names_panic() {
Identifier::new("茶".repeat(22));
}
#[test]
fn short_names_are_fine() {
Identifier::new("t".repeat(64));
Identifier::new("茶".repeat(21));
}
}