Skip to main content

apache_avro/schema/
name.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::{
19    AvroResult, Error, Schema,
20    error::Details,
21    util::{JsonValueDescriber, MapHelper},
22    validator::{validate_namespace, validate_schema_name},
23};
24use serde::{Deserialize, Serialize, Serializer};
25use serde_json::{Map, Value};
26use std::borrow::Cow;
27use std::collections::HashMap;
28use std::fmt::{Debug, Display, Formatter};
29use std::str::FromStr;
30use std::sync::Arc;
31
32/// Represents names for `record`, `enum` and `fixed` Avro schemas.
33///
34/// Each of these `Schema`s have a `fullname` composed of two parts:
35///   * a name
36///   * a namespace
37///
38/// `aliases` can also be defined, to facilitate schema evolution.
39///
40/// More information about schema names can be found in the
41/// [Avro specification](https://avro.apache.org/docs/++version++/specification/#names)
42#[derive(Clone, Hash, PartialEq, Eq)]
43pub struct Name {
44    /// The full name
45    namespace_and_name: Arc<str>,
46    /// Start byte of the name part
47    ///
48    /// If this is zero, then there is no namespace.
49    index_of_name: usize,
50}
51
52/// Represents the aliases for Named Schema
53pub type Aliases = Option<Vec<Alias>>;
54/// Represents Schema lookup within a schema env
55pub type Names = HashMap<Name, Schema>;
56/// Represents Schema lookup within a schema
57pub type NamesRef<'a> = HashMap<Name, &'a Schema>;
58/// Represents the namespace for Named Schema
59pub type Namespace = Option<String>;
60/// Represents the namespace for Named Schema
61pub type NamespaceRef<'a> = Option<&'a str>;
62
63impl Name {
64    /// Create a new `Name`.
65    /// Parses the optional `namespace` from the `name` string.
66    /// `aliases` will not be defined.
67    pub fn new(name: impl Into<String> + AsRef<str>) -> AvroResult<Self> {
68        Self::new_with_enclosing_namespace(name, None)
69    }
70
71    /// Create a new `Name` using the namespace from `enclosing_namespace` if absent.
72    pub fn new_with_enclosing_namespace(
73        name: impl Into<String> + AsRef<str>,
74        enclosing_namespace: NamespaceRef,
75    ) -> AvroResult<Self> {
76        // Having both `Into<String>` and `AsRef<str>` allows optimal use in both of these cases:
77        // - `name` is a `String`. We can reuse the allocation when `enclosing_namespace` is `None`
78        //   or `name` already has a namespace.
79        // - `name` is a `str`. With only `Into<String` we need an extra allocation in the case `name`
80        //   doesn't have namespace and `enclosing_namespace` is `Some`. Having `AsRef<str>` allows
81        //   skipping that allocation.
82        let name_ref = name.as_ref();
83        let index_of_name = validate_schema_name(name_ref)?;
84        if index_of_name > name_ref.len() {
85            return Err(Details::InvalidSchemaNameValidatorImplementation.into());
86        }
87
88        if index_of_name == 0
89            && let Some(namespace) = enclosing_namespace
90            && !namespace.is_empty()
91        {
92            validate_namespace(namespace)?;
93            Ok(Self {
94                namespace_and_name: format!("{namespace}.{name_ref}").into(),
95                index_of_name: namespace.len() + 1,
96            })
97        } else if index_of_name == 1 {
98            // Name has a leading dot
99            Ok(Self {
100                namespace_and_name: name.as_ref()[1..].into(),
101                index_of_name: 0,
102            })
103        } else {
104            Ok(Self {
105                namespace_and_name: Arc::from(name.into()),
106                index_of_name,
107            })
108        }
109    }
110
111    /// Parse a `serde_json::Value` into a `Name`.
112    pub(crate) fn parse(
113        complex: &mut Map<String, Value>,
114        enclosing_namespace: NamespaceRef,
115    ) -> AvroResult<Self> {
116        let name_field = complex.name()?;
117        let namespace = match complex.remove("namespace") {
118            Some(Value::String(s)) => Some(s),
119            Some(Value::Null) | None => None,
120            Some(value) => {
121                return Err(Details::GetNamespaceFieldWrongType(value.description()).into());
122            }
123        };
124        Self::new_with_enclosing_namespace(name_field, namespace.as_deref().or(enclosing_namespace))
125    }
126
127    pub fn name(&self) -> &str {
128        &self.namespace_and_name[self.index_of_name..]
129    }
130
131    pub fn namespace(&self) -> NamespaceRef<'_> {
132        if self.index_of_name == 0 {
133            None
134        } else {
135            Some(&self.namespace_and_name[..(self.index_of_name - 1)])
136        }
137    }
138
139    /// Return the `fullname` of this `Name`
140    ///
141    /// More information about fullnames can be found in the
142    /// [Avro specification](https://avro.apache.org/docs/++version++/specification/#names)
143    pub fn fullname(&self, enclosing_namespace: NamespaceRef) -> String {
144        if self.index_of_name == 0
145            && let Some(namespace) = enclosing_namespace
146            && !namespace.is_empty()
147        {
148            format!("{namespace}.{}", self.namespace_and_name)
149        } else {
150            self.namespace_and_name.to_string()
151        }
152    }
153
154    /// Construct the fully qualified name
155    ///
156    /// ```
157    /// # use apache_avro::{Error, schema::Name};
158    /// assert_eq!(
159    ///     Name::new("some_name")?.fully_qualified_name(Some("some_namespace")).into_owned(),
160    ///     Name::new("some_namespace.some_name")?
161    /// );
162    /// assert_eq!(
163    ///     Name::new("some_namespace.some_name")?.fully_qualified_name(Some("other_namespace")).into_owned(),
164    ///     Name::new("some_namespace.some_name")?
165    /// );
166    /// # Ok::<(), Error>(())
167    /// ```
168    pub fn fully_qualified_name(&self, enclosing_namespace: NamespaceRef) -> Cow<'_, Name> {
169        if self.index_of_name == 0
170            && let Some(namespace) = enclosing_namespace
171            && !namespace.is_empty()
172        {
173            Cow::Owned(Self {
174                namespace_and_name: format!("{namespace}.{}", self.namespace_and_name).into(),
175                index_of_name: namespace.len() + 1,
176            })
177        } else {
178            Cow::Borrowed(self)
179        }
180    }
181
182    /// Create an empty name.
183    ///
184    /// This name is invalid and should never be used anywhere! The only valid use is filling
185    /// a `Name` field that will not be used.
186    ///
187    /// Using this name will cause a panic.
188    pub(crate) fn invalid_empty_name() -> Self {
189        Self {
190            namespace_and_name: Arc::default(),
191            index_of_name: usize::MAX,
192        }
193    }
194}
195
196impl TryFrom<&str> for Name {
197    type Error = Error;
198
199    fn try_from(value: &str) -> Result<Self, Self::Error> {
200        Self::new(value)
201    }
202}
203
204impl TryFrom<String> for Name {
205    type Error = Error;
206
207    fn try_from(value: String) -> Result<Self, Self::Error> {
208        Self::new(&value)
209    }
210}
211
212impl FromStr for Name {
213    type Err = Error;
214
215    fn from_str(s: &str) -> Result<Self, Self::Err> {
216        Self::new(s)
217    }
218}
219
220impl Debug for Name {
221    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
222        if self.index_of_name > self.namespace_and_name.len() {
223            f.debug_tuple("Name").field(&"Invalid name!").finish()
224        } else {
225            let mut debug = f.debug_struct("Name");
226            debug.field("name", &self.name());
227            if self.index_of_name != 0 {
228                debug.field("namespace", &self.namespace());
229                debug.finish()
230            } else {
231                debug.finish_non_exhaustive()
232            }
233        }
234    }
235}
236
237impl AsRef<str> for Name {
238    fn as_ref(&self) -> &str {
239        self.namespace_and_name.as_ref()
240    }
241}
242
243impl Display for Name {
244    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
245        assert!(
246            self.index_of_name <= self.namespace_and_name.len(),
247            "Invalid name used"
248        );
249        f.write_str(&self.namespace_and_name)
250    }
251}
252
253impl<'de> Deserialize<'de> for Name {
254    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
255    where
256        D: serde::de::Deserializer<'de>,
257    {
258        Value::deserialize(deserializer).and_then(|value| {
259            use serde::de::Error;
260            if let Value::Object(mut json) = value {
261                Name::parse(&mut json, None).map_err(Error::custom)
262            } else {
263                Err(Error::custom(format!("Expected a JSON object: {value:?}")))
264            }
265        })
266    }
267}
268
269/// Newtype pattern for `Name` to better control the `serde_json::Value` representation.
270///
271/// Aliases are serialized as an array of plain strings in the JSON representation.
272#[derive(Clone, Debug, Hash, PartialEq, Eq)]
273pub struct Alias(Name);
274
275impl Alias {
276    pub fn new(name: impl Into<String> + AsRef<str>) -> AvroResult<Self> {
277        Name::new(name).map(Self)
278    }
279
280    /// Create a new `Alias` using the namespace from `enclosing_namespace` if absent.
281    pub fn new_with_enclosing_namespace(
282        name: impl Into<String> + AsRef<str>,
283        enclosing_namespace: NamespaceRef,
284    ) -> AvroResult<Self> {
285        Name::new_with_enclosing_namespace(name, enclosing_namespace).map(Self)
286    }
287
288    pub fn name(&self) -> &str {
289        self.0.name()
290    }
291
292    pub fn namespace(&self) -> NamespaceRef<'_> {
293        self.0.namespace()
294    }
295
296    pub fn fullname(&self, enclosing_namespace: NamespaceRef) -> String {
297        self.0.fullname(enclosing_namespace)
298    }
299
300    pub fn fully_qualified_name(&self, default_namespace: NamespaceRef) -> Cow<'_, Name> {
301        self.0.fully_qualified_name(default_namespace)
302    }
303}
304
305impl TryFrom<&str> for Alias {
306    type Error = Error;
307
308    fn try_from(value: &str) -> Result<Self, Self::Error> {
309        Self::new(value)
310    }
311}
312
313impl TryFrom<String> for Alias {
314    type Error = Error;
315
316    fn try_from(value: String) -> Result<Self, Self::Error> {
317        Self::new(&value)
318    }
319}
320
321impl FromStr for Alias {
322    type Err = Error;
323
324    fn from_str(s: &str) -> Result<Self, Self::Err> {
325        Self::new(s)
326    }
327}
328
329impl Serialize for Alias {
330    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
331    where
332        S: Serializer,
333    {
334        serializer.serialize_str(&self.fullname(None))
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use crate::Error;
341
342    use super::*;
343    use apache_avro_test_helper::TestResult;
344
345    #[test]
346    /// Zero-length namespace is considered as no-namespace.
347    fn test_namespace_from_name_with_empty_value() -> TestResult {
348        let name = Name::new(".name")?;
349        assert_eq!(name.namespace_and_name.as_ref(), "name");
350        assert_eq!(name.index_of_name, 0);
351
352        Ok(())
353    }
354
355    #[test]
356    /// Whitespace is not allowed in the name.
357    fn test_name_with_whitespace_value() {
358        match Name::new(" ").map_err(Error::into_details) {
359            Err(Details::InvalidSchemaName(_, _)) => {}
360            _ => panic!("Expected an Details::InvalidSchemaName!"),
361        }
362    }
363
364    #[test]
365    /// The name must be non-empty.
366    fn test_name_with_no_name_part() {
367        match Name::new("space.").map_err(Error::into_details) {
368            Err(Details::InvalidSchemaName(_, _)) => {}
369            _ => panic!("Expected an Details::InvalidSchemaName!"),
370        }
371    }
372
373    /// A test cases showing that names and namespaces can be constructed
374    /// entirely by underscores.
375    #[test]
376    fn test_avro_3897_funny_valid_names_and_namespaces() -> TestResult {
377        for funny_name in ["_", "_._", "__._", "_.__", "_._._"] {
378            let name = Name::new(funny_name);
379            assert!(name.is_ok());
380        }
381        Ok(())
382    }
383}