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 serde::{Deserialize, Serialize, Serializer};
19use serde_json::{Map, Value};
20use std::collections::HashMap;
21use std::fmt;
22use std::str::FromStr;
23
24use crate::{
25    AvroResult, Error, Schema,
26    error::Details,
27    util::MapHelper,
28    validator::{validate_namespace, validate_schema_name},
29};
30
31/// Represents names for `record`, `enum` and `fixed` Avro schemas.
32///
33/// Each of these `Schema`s have a `fullname` composed of two parts:
34///   * a name
35///   * a namespace
36///
37/// `aliases` can also be defined, to facilitate schema evolution.
38///
39/// More information about schema names can be found in the
40/// [Avro specification](https://avro.apache.org/docs/current/specification/#names)
41#[derive(Clone, Debug, Hash, PartialEq, Eq)]
42pub struct Name {
43    pub name: String,
44    pub namespace: Namespace,
45}
46
47/// Represents the aliases for Named Schema
48pub type Aliases = Option<Vec<Alias>>;
49/// Represents Schema lookup within a schema env
50pub type Names = HashMap<Name, Schema>;
51/// Represents Schema lookup within a schema
52pub type NamesRef<'a> = HashMap<Name, &'a Schema>;
53/// Represents the namespace for Named Schema
54pub type Namespace = Option<String>;
55
56impl Name {
57    /// Create a new `Name`.
58    /// Parses the optional `namespace` from the `name` string.
59    /// `aliases` will not be defined.
60    pub fn new(name: &str) -> AvroResult<Self> {
61        let (name, namespace) = Name::get_name_and_namespace(name)?;
62        Ok(Self {
63            name,
64            namespace: namespace.filter(|ns| !ns.is_empty()),
65        })
66    }
67
68    fn get_name_and_namespace(name: &str) -> AvroResult<(String, Namespace)> {
69        validate_schema_name(name)
70    }
71
72    /// Parse a `serde_json::Value` into a `Name`.
73    pub(crate) fn parse(
74        complex: &Map<String, Value>,
75        enclosing_namespace: &Namespace,
76    ) -> AvroResult<Self> {
77        let (name, namespace_from_name) = complex
78            .name()
79            .map(|name| Name::get_name_and_namespace(name.as_str()).unwrap())
80            .ok_or(Details::GetNameField)?;
81        // FIXME Reading name from the type is wrong ! The name there is just a metadata (AVRO-3430)
82        let type_name = match complex.get("type") {
83            Some(Value::Object(complex_type)) => complex_type.name().or(None),
84            _ => None,
85        };
86
87        let namespace = namespace_from_name
88            .or_else(|| {
89                complex
90                    .string("namespace")
91                    .or_else(|| enclosing_namespace.clone())
92            })
93            .filter(|ns| !ns.is_empty());
94
95        if let Some(ref ns) = namespace {
96            validate_namespace(ns)?;
97        }
98
99        Ok(Self {
100            name: type_name.unwrap_or(name),
101            namespace,
102        })
103    }
104
105    /// Return the `fullname` of this `Name`
106    ///
107    /// More information about fullnames can be found in the
108    /// [Avro specification](https://avro.apache.org/docs/current/specification/#names)
109    pub fn fullname(&self, default_namespace: Namespace) -> String {
110        if self.name.contains('.') {
111            self.name.clone()
112        } else {
113            let namespace = self.namespace.clone().or(default_namespace);
114
115            match namespace {
116                Some(ref namespace) if !namespace.is_empty() => {
117                    format!("{}.{}", namespace, self.name)
118                }
119                _ => self.name.clone(),
120            }
121        }
122    }
123
124    /// Return the fully qualified name needed for indexing or searching for the schema within a schema/schema env context. Puts the enclosing namespace into the name's namespace for clarity in schema/schema env parsing
125    /// ```ignore
126    /// use apache_avro::schema::Name;
127    ///
128    /// assert_eq!(
129    /// Name::new("some_name")?.fully_qualified_name(&Some("some_namespace".into())),
130    /// Name::new("some_namespace.some_name")?
131    /// );
132    /// assert_eq!(
133    /// Name::new("some_namespace.some_name")?.fully_qualified_name(&Some("other_namespace".into())),
134    /// Name::new("some_namespace.some_name")?
135    /// );
136    /// ```
137    pub fn fully_qualified_name(&self, enclosing_namespace: &Namespace) -> Name {
138        Name {
139            name: self.name.clone(),
140            namespace: self
141                .namespace
142                .clone()
143                .or_else(|| enclosing_namespace.clone().filter(|ns| !ns.is_empty())),
144        }
145    }
146}
147
148impl TryFrom<&str> for Name {
149    type Error = Error;
150
151    fn try_from(value: &str) -> Result<Self, Self::Error> {
152        Self::new(value)
153    }
154}
155
156impl TryFrom<String> for Name {
157    type Error = Error;
158
159    fn try_from(value: String) -> Result<Self, Self::Error> {
160        Self::new(&value)
161    }
162}
163
164impl FromStr for Name {
165    type Err = Error;
166
167    fn from_str(s: &str) -> Result<Self, Self::Err> {
168        Self::new(s)
169    }
170}
171
172impl fmt::Display for Name {
173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174        f.write_str(&self.fullname(None)[..])
175    }
176}
177
178impl<'de> Deserialize<'de> for Name {
179    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
180    where
181        D: serde::de::Deserializer<'de>,
182    {
183        Value::deserialize(deserializer).and_then(|value| {
184            use serde::de::Error;
185            if let Value::Object(json) = value {
186                Name::parse(&json, &None).map_err(Error::custom)
187            } else {
188                Err(Error::custom(format!("Expected a JSON object: {value:?}")))
189            }
190        })
191    }
192}
193
194/// Newtype pattern for `Name` to better control the `serde_json::Value` representation.
195/// Aliases are serialized as an array of plain strings in the JSON representation.
196#[derive(Clone, Debug, Hash, PartialEq, Eq)]
197pub struct Alias(Name);
198
199impl Alias {
200    pub fn new(name: &str) -> AvroResult<Self> {
201        Name::new(name).map(Self)
202    }
203
204    pub fn name(&self) -> String {
205        self.0.name.clone()
206    }
207
208    pub fn namespace(&self) -> Namespace {
209        self.0.namespace.clone()
210    }
211
212    pub fn fullname(&self, default_namespace: Namespace) -> String {
213        self.0.fullname(default_namespace)
214    }
215
216    pub fn fully_qualified_name(&self, default_namespace: &Namespace) -> Name {
217        self.0.fully_qualified_name(default_namespace)
218    }
219}
220
221impl TryFrom<&str> for Alias {
222    type Error = Error;
223
224    fn try_from(value: &str) -> Result<Self, Self::Error> {
225        Self::new(value)
226    }
227}
228
229impl TryFrom<String> for Alias {
230    type Error = Error;
231
232    fn try_from(value: String) -> Result<Self, Self::Error> {
233        Self::new(&value)
234    }
235}
236
237impl FromStr for Alias {
238    type Err = Error;
239
240    fn from_str(s: &str) -> Result<Self, Self::Err> {
241        Self::new(s)
242    }
243}
244
245impl Serialize for Alias {
246    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
247    where
248        S: Serializer,
249    {
250        serializer.serialize_str(&self.fullname(None))
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use crate::Error;
257
258    use super::*;
259    use apache_avro_test_helper::TestResult;
260
261    #[test]
262    /// Zero-length namespace is considered as no-namespace.
263    fn test_namespace_from_name_with_empty_value() -> TestResult {
264        let name = Name::new(".name")?;
265        assert_eq!(name.name, "name");
266        assert_eq!(name.namespace, None);
267
268        Ok(())
269    }
270
271    #[test]
272    /// Whitespace is not allowed in the name.
273    fn test_name_with_whitespace_value() {
274        match Name::new(" ").map_err(Error::into_details) {
275            Err(Details::InvalidSchemaName(_, _)) => {}
276            _ => panic!("Expected an Details::InvalidSchemaName!"),
277        }
278    }
279
280    #[test]
281    /// The name must be non-empty.
282    fn test_name_with_no_name_part() {
283        match Name::new("space.").map_err(Error::into_details) {
284            Err(Details::InvalidSchemaName(_, _)) => {}
285            _ => panic!("Expected an Details::InvalidSchemaName!"),
286        }
287    }
288
289    /// A test cases showing that names and namespaces can be constructed
290    /// entirely by underscores.
291    #[test]
292    fn test_avro_3897_funny_valid_names_and_namespaces() -> TestResult {
293        for funny_name in ["_", "_._", "__._", "_.__", "_._._"] {
294            let name = Name::new(funny_name);
295            assert!(name.is_ok());
296        }
297        Ok(())
298    }
299}