use serde::{Deserialize, Serialize, Serializer};
use serde_json::{Map, Value};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
use std::str::FromStr;
use crate::{
AvroResult, Error, Schema,
error::Details,
util::MapHelper,
validator::{validate_namespace, validate_schema_name},
};
#[derive(Clone, Hash, PartialEq, Eq)]
pub struct Name {
namespace_and_name: String,
index_of_name: usize,
}
pub type Aliases = Option<Vec<Alias>>;
pub type Names = HashMap<Name, Schema>;
pub type NamesRef<'a> = HashMap<Name, &'a Schema>;
pub type Namespace = Option<String>;
pub type NamespaceRef<'a> = Option<&'a str>;
impl Name {
pub fn new(name: impl Into<String> + AsRef<str>) -> AvroResult<Self> {
Self::new_with_enclosing_namespace(name, None)
}
pub fn new_with_enclosing_namespace(
name: impl Into<String> + AsRef<str>,
enclosing_namespace: NamespaceRef,
) -> AvroResult<Self> {
let name_ref = name.as_ref();
let index_of_name = validate_schema_name(name_ref)?;
if index_of_name > name_ref.len() {
return Err(Details::InvalidSchemaNameValidatorImplementation.into());
}
if index_of_name == 0
&& let Some(namespace) = enclosing_namespace
&& !namespace.is_empty()
{
validate_namespace(namespace)?;
Ok(Self {
namespace_and_name: format!("{namespace}.{name_ref}"),
index_of_name: namespace.len() + 1,
})
} else if index_of_name == 1 {
Ok(Self {
namespace_and_name: name.as_ref()[1..].into(),
index_of_name: 0,
})
} else {
Ok(Self {
namespace_and_name: name.into(),
index_of_name,
})
}
}
pub(crate) fn parse(
complex: &Map<String, Value>,
enclosing_namespace: NamespaceRef,
) -> AvroResult<Self> {
let name_field = complex.name().ok_or(Details::GetNameField)?;
Self::new_with_enclosing_namespace(
name_field,
complex.string("namespace").or(enclosing_namespace),
)
}
pub fn name(&self) -> &str {
&self.namespace_and_name[self.index_of_name..]
}
pub fn namespace(&self) -> NamespaceRef<'_> {
if self.index_of_name == 0 {
None
} else {
Some(&self.namespace_and_name[..(self.index_of_name - 1)])
}
}
pub fn fullname(&self, enclosing_namespace: NamespaceRef) -> String {
if self.index_of_name == 0
&& let Some(namespace) = enclosing_namespace
&& !namespace.is_empty()
{
format!("{namespace}.{}", self.namespace_and_name)
} else {
self.namespace_and_name.clone()
}
}
pub fn fully_qualified_name(&self, enclosing_namespace: NamespaceRef) -> Cow<'_, Name> {
if self.index_of_name == 0
&& let Some(namespace) = enclosing_namespace
&& !namespace.is_empty()
{
Cow::Owned(Self {
namespace_and_name: format!("{namespace}.{}", self.namespace_and_name),
index_of_name: namespace.len() + 1,
})
} else {
Cow::Borrowed(self)
}
}
pub(crate) fn invalid_empty_name() -> Self {
Self {
namespace_and_name: String::new(),
index_of_name: usize::MAX,
}
}
}
impl TryFrom<&str> for Name {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<String> for Name {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(&value)
}
}
impl FromStr for Name {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl Debug for Name {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if self.index_of_name > self.namespace_and_name.len() {
f.debug_tuple("Name").field(&"Invalid name!").finish()
} else {
let mut debug = f.debug_struct("Name");
debug.field("name", &self.name());
if self.index_of_name != 0 {
debug.field("namespace", &self.namespace());
debug.finish()
} else {
debug.finish_non_exhaustive()
}
}
}
}
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
self.namespace_and_name.as_ref()
}
}
impl Display for Name {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
assert!(
self.index_of_name <= self.namespace_and_name.len(),
"Invalid name used"
);
f.write_str(&self.namespace_and_name)
}
}
impl<'de> Deserialize<'de> for Name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
Value::deserialize(deserializer).and_then(|value| {
use serde::de::Error;
if let Value::Object(json) = value {
Name::parse(&json, None).map_err(Error::custom)
} else {
Err(Error::custom(format!("Expected a JSON object: {value:?}")))
}
})
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Alias(Name);
impl Alias {
pub fn new(name: impl Into<String> + AsRef<str>) -> AvroResult<Self> {
Name::new(name).map(Self)
}
pub fn new_with_enclosing_namespace(
name: impl Into<String> + AsRef<str>,
enclosing_namespace: NamespaceRef,
) -> AvroResult<Self> {
Name::new_with_enclosing_namespace(name, enclosing_namespace).map(Self)
}
pub fn name(&self) -> &str {
self.0.name()
}
pub fn namespace(&self) -> NamespaceRef<'_> {
self.0.namespace()
}
pub fn fullname(&self, enclosing_namespace: NamespaceRef) -> String {
self.0.fullname(enclosing_namespace)
}
pub fn fully_qualified_name(&self, default_namespace: NamespaceRef) -> Cow<'_, Name> {
self.0.fully_qualified_name(default_namespace)
}
}
impl TryFrom<&str> for Alias {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl TryFrom<String> for Alias {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(&value)
}
}
impl FromStr for Alias {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl Serialize for Alias {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.fullname(None))
}
}
#[cfg(test)]
mod tests {
use crate::Error;
use super::*;
use apache_avro_test_helper::TestResult;
#[test]
fn test_namespace_from_name_with_empty_value() -> TestResult {
let name = Name::new(".name")?;
assert_eq!(name.namespace_and_name, "name");
assert_eq!(name.index_of_name, 0);
Ok(())
}
#[test]
fn test_name_with_whitespace_value() {
match Name::new(" ").map_err(Error::into_details) {
Err(Details::InvalidSchemaName(_, _)) => {}
_ => panic!("Expected an Details::InvalidSchemaName!"),
}
}
#[test]
fn test_name_with_no_name_part() {
match Name::new("space.").map_err(Error::into_details) {
Err(Details::InvalidSchemaName(_, _)) => {}
_ => panic!("Expected an Details::InvalidSchemaName!"),
}
}
#[test]
fn test_avro_3897_funny_valid_names_and_namespaces() -> TestResult {
for funny_name in ["_", "_._", "__._", "_.__", "_._._"] {
let name = Name::new(funny_name);
assert!(name.is_ok());
}
Ok(())
}
}