blob: 3b2acc39dfb883797028ac7f7c80890d603545fc [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::error::Details;
use crate::schema::record::RecordSchemaParseLocation;
use crate::schema::{
Alias, Aliases, ArraySchema, DecimalMetadata, DecimalSchema, EnumSchema, FixedSchema,
MapSchema, Name, Names, Namespace, Precision, RecordField, RecordSchema, Scale, Schema,
SchemaKind, UnionSchema, UuidSchema,
};
use crate::types;
use crate::util::MapHelper;
use crate::validator::validate_enum_symbol_name;
use crate::{AvroResult, Error};
use log::{debug, error, warn};
use serde_json::{Map, Value};
use std::collections::{BTreeMap, HashMap, HashSet};
#[derive(Default)]
pub(crate) struct Parser {
input_schemas: HashMap<Name, Value>,
/// Used to resolve cyclic references, i.e. when a
/// field's type is a reference to its record's type
resolving_schemas: Names,
input_order: Vec<Name>,
/// Used to avoid parsing the same schema twice
parsed_schemas: Names,
}
impl Parser {
pub(crate) fn new(
input_schemas: HashMap<Name, Value>,
input_order: Vec<Name>,
parsed_schemas: Names,
) -> Self {
Self {
input_schemas,
resolving_schemas: HashMap::default(),
input_order,
parsed_schemas,
}
}
pub(crate) fn get_parsed_schemas(&mut self) -> &Names {
&self.parsed_schemas
}
/// Create a `Schema` from a string representing a JSON Avro schema.
pub(super) fn parse_str(&mut self, input: &str) -> AvroResult<Schema> {
let value = serde_json::from_str(input).map_err(Details::ParseSchemaJson)?;
self.parse(&value, &None)
}
/// Create an array of `Schema`s from an iterator of JSON Avro schemas.
///
/// It is allowed that the schemas have cross-dependencies; these will be resolved during parsing.
pub(super) fn parse_list(&mut self) -> AvroResult<Vec<Schema>> {
self.parse_input_schemas()?;
let mut parsed_schemas = Vec::with_capacity(self.parsed_schemas.len());
for name in self.input_order.drain(0..) {
let parsed = self
.parsed_schemas
.remove(&name)
.expect("One of the input schemas was unexpectedly not parsed");
parsed_schemas.push(parsed);
}
Ok(parsed_schemas)
}
/// Convert the input schemas to `parsed_schemas`.
pub(super) fn parse_input_schemas(&mut self) -> Result<(), Error> {
while !self.input_schemas.is_empty() {
let next_name = self
.input_schemas
.keys()
.next()
.expect("Input schemas unexpectedly empty")
.to_owned();
let (name, value) = self
.input_schemas
.remove_entry(&next_name)
.expect("Key unexpectedly missing");
let parsed = self.parse(&value, &None)?;
self.parsed_schemas
.insert(self.get_schema_type_name(name, value), parsed);
}
Ok(())
}
/// Create a `Schema` from a `serde_json::Value` representing a JSON Avro schema.
pub(super) fn parse(
&mut self,
value: &Value,
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
match *value {
Value::String(ref t) => self.parse_known_schema(t.as_str(), enclosing_namespace),
Value::Object(ref data) => {
self.parse_complex(data, enclosing_namespace, RecordSchemaParseLocation::Root)
}
Value::Array(ref data) => self.parse_union(data, enclosing_namespace),
_ => Err(Details::ParseSchemaFromValidJson.into()),
}
}
/// Parse a string as a primitive type or reference to `parsed_schemas`.
fn parse_known_schema(
&mut self,
name: &str,
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
match name {
"null" => Ok(Schema::Null),
"boolean" => Ok(Schema::Boolean),
"int" => Ok(Schema::Int),
"long" => Ok(Schema::Long),
"double" => Ok(Schema::Double),
"float" => Ok(Schema::Float),
"bytes" => Ok(Schema::Bytes),
"string" => Ok(Schema::String),
_ => self.fetch_schema_ref(name, enclosing_namespace),
}
}
/// Given a name, tries to retrieve the parsed schema from `parsed_schemas`.
///
/// If a parsed schema is not found, it checks if a currently resolving
/// schema with that name exists.
/// If a resolving schema is not found, it checks if a JSON with that name exists
/// in `input_schemas` and then parses it (removing it from `input_schemas`)
/// and adds the parsed schema to `parsed_schemas`.
///
/// This method allows schemas definitions that depend on other types to
/// parse their dependencies (or look them up if already parsed).
pub(super) fn fetch_schema_ref(
&mut self,
name: &str,
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
fn get_schema_ref(parsed: &Schema) -> Schema {
match parsed {
&Schema::Record(RecordSchema { ref name, .. })
| &Schema::Enum(EnumSchema { ref name, .. })
| &Schema::Fixed(FixedSchema { ref name, .. }) => {
Schema::Ref { name: name.clone() }
}
_ => parsed.clone(),
}
}
let name = Name::new(name)?;
let fully_qualified_name = name.fully_qualified_name(enclosing_namespace);
if self.parsed_schemas.contains_key(&fully_qualified_name) {
return Ok(Schema::Ref {
name: fully_qualified_name,
});
}
if let Some(resolving_schema) = self.resolving_schemas.get(&fully_qualified_name) {
return Ok(resolving_schema.clone());
}
// For good error reporting we add this check
match name.name.as_str() {
"record" | "enum" | "fixed" => {
return Err(Details::InvalidSchemaRecord(name.to_string()).into());
}
_ => (),
}
let value = self
.input_schemas
.remove(&fully_qualified_name)
// TODO make a better descriptive error message here that conveys that a named schema cannot be found
.ok_or_else(|| {
let full_name = fully_qualified_name.fullname(None);
if full_name == "bool" {
Details::ParsePrimitiveSimilar(full_name, "boolean")
} else {
Details::ParsePrimitive(full_name)
}
})?;
// parsing a full schema from inside another schema. Other full schema will not inherit namespace
let parsed = self.parse(&value, &None)?;
self.parsed_schemas
.insert(self.get_schema_type_name(name, value), parsed.clone());
Ok(get_schema_ref(&parsed))
}
fn get_decimal_integer(
&self,
complex: &Map<String, Value>,
key: &'static str,
) -> AvroResult<DecimalMetadata> {
match complex.get(key) {
Some(Value::Number(value)) => self.parse_json_integer_for_decimal(value),
None => {
if key == "scale" {
Ok(0)
} else {
Err(Details::GetDecimalMetadataFromJson(key).into())
}
}
Some(value) => Err(Details::GetDecimalMetadataValueFromJson {
key: key.into(),
value: value.clone(),
}
.into()),
}
}
fn parse_precision_and_scale(
&self,
complex: &Map<String, Value>,
) -> AvroResult<(Precision, Scale)> {
let precision = self.get_decimal_integer(complex, "precision")?;
let scale = self.get_decimal_integer(complex, "scale")?;
if precision < 1 {
return Err(Details::DecimalPrecisionMuBePositive { precision }.into());
}
if precision < scale {
Err(Details::DecimalPrecisionLessThanScale { precision, scale }.into())
} else {
Ok((precision, scale))
}
}
/// Parse a `serde_json::Value` representing a complex Avro type into a `Schema`.
///
/// Avro supports "recursive" definition of types.
/// e.g: `{"type": {"type": "string"}}`
pub(super) fn parse_complex(
&mut self,
complex: &Map<String, Value>,
enclosing_namespace: &Namespace,
parse_location: RecordSchemaParseLocation,
) -> AvroResult<Schema> {
// Try to parse this as a native complex type.
fn parse_as_native_complex(
complex: &Map<String, Value>,
parser: &mut Parser,
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
match complex.get("type") {
Some(value) => match value {
Value::String(s) if s == "fixed" => {
parser.parse_fixed(complex, enclosing_namespace)
}
_ => parser.parse(value, enclosing_namespace),
},
None => Err(Details::GetLogicalTypeField.into()),
}
}
// This crate supports some logical types natively, and this function tries to convert
// a native complex type with a logical type attribute to these logical types.
// This function:
// 1. Checks whether the native complex type is in the supported kinds.
// 2. If it is, using the convert function to convert the native complex type to
// a logical type.
fn try_convert_to_logical_type<F>(
logical_type: &str,
schema: Schema,
supported_schema_kinds: &[SchemaKind],
convert: F,
) -> AvroResult<Schema>
where
F: Fn(Schema) -> AvroResult<Schema>,
{
let kind = SchemaKind::from(schema.clone());
if supported_schema_kinds.contains(&kind) {
convert(schema)
} else {
warn!(
"Ignoring unknown logical type '{logical_type}' for schema of type: {schema:?}!"
);
Ok(schema)
}
}
match complex.get("logicalType") {
Some(Value::String(t)) => match t.as_str() {
"decimal" => {
return try_convert_to_logical_type(
"decimal",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Fixed, SchemaKind::Bytes],
|inner| -> AvroResult<Schema> {
match self.parse_precision_and_scale(complex) {
Ok((precision, scale)) => Ok(Schema::Decimal(DecimalSchema {
precision,
scale,
inner: inner.try_into()?,
})),
Err(err) => {
warn!("Ignoring invalid decimal logical type: {err}");
Ok(inner)
}
}
},
);
}
"big-decimal" => {
return try_convert_to_logical_type(
"big-decimal",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Bytes],
|_| -> AvroResult<Schema> { Ok(Schema::BigDecimal) },
);
}
"uuid" => {
return try_convert_to_logical_type(
"uuid",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::String, SchemaKind::Fixed, SchemaKind::Bytes],
|schema| match schema {
Schema::String => Ok(Schema::Uuid(UuidSchema::String)),
Schema::Fixed(fixed @ FixedSchema { size: 16, .. }) => {
Ok(Schema::Uuid(UuidSchema::Fixed(fixed)))
}
Schema::Fixed(FixedSchema { size, .. }) => {
warn!(
"Ignoring uuid logical type for a Fixed schema because its size ({size:?}) is not 16! Schema: {schema:?}"
);
Ok(schema)
}
Schema::Bytes => Ok(Schema::Uuid(UuidSchema::Bytes)),
_ => {
warn!("Ignoring invalid uuid logical type for schema: {schema:?}");
Ok(schema)
}
},
);
}
"date" => {
return try_convert_to_logical_type(
"date",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Int],
|_| -> AvroResult<Schema> { Ok(Schema::Date) },
);
}
"time-millis" => {
return try_convert_to_logical_type(
"date",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Int],
|_| -> AvroResult<Schema> { Ok(Schema::TimeMillis) },
);
}
"time-micros" => {
return try_convert_to_logical_type(
"time-micros",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Long],
|_| -> AvroResult<Schema> { Ok(Schema::TimeMicros) },
);
}
"timestamp-millis" => {
return try_convert_to_logical_type(
"timestamp-millis",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Long],
|_| -> AvroResult<Schema> { Ok(Schema::TimestampMillis) },
);
}
"timestamp-micros" => {
return try_convert_to_logical_type(
"timestamp-micros",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Long],
|_| -> AvroResult<Schema> { Ok(Schema::TimestampMicros) },
);
}
"timestamp-nanos" => {
return try_convert_to_logical_type(
"timestamp-nanos",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Long],
|_| -> AvroResult<Schema> { Ok(Schema::TimestampNanos) },
);
}
"local-timestamp-millis" => {
return try_convert_to_logical_type(
"local-timestamp-millis",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Long],
|_| -> AvroResult<Schema> { Ok(Schema::LocalTimestampMillis) },
);
}
"local-timestamp-micros" => {
return try_convert_to_logical_type(
"local-timestamp-micros",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Long],
|_| -> AvroResult<Schema> { Ok(Schema::LocalTimestampMicros) },
);
}
"local-timestamp-nanos" => {
return try_convert_to_logical_type(
"local-timestamp-nanos",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Long],
|_| -> AvroResult<Schema> { Ok(Schema::LocalTimestampNanos) },
);
}
"duration" => {
return try_convert_to_logical_type(
"duration",
parse_as_native_complex(complex, self, enclosing_namespace)?,
&[SchemaKind::Fixed],
|schema| -> AvroResult<Schema> {
match schema {
Schema::Fixed(fixed @ FixedSchema { size: 12, .. }) => {
Ok(Schema::Duration(fixed))
}
Schema::Fixed(FixedSchema { size, .. }) => {
warn!(
"Ignoring duration logical type on fixed type because size ({size}) is not 12! Schema: {schema:?}"
);
Ok(schema)
}
_ => {
warn!(
"Ignoring invalid duration logical type for schema: {schema:?}"
);
Ok(schema)
}
}
},
);
}
// In this case, of an unknown logical type, we just pass through the underlying
// type.
_ => {}
},
// The spec says to ignore invalid logical types and just pass through the
// underlying type. It is unclear whether that applies to this case or not, where the
// `logicalType` is not a string.
Some(value) => return Err(Details::GetLogicalTypeFieldType(value.clone()).into()),
_ => {}
}
match complex.get("type") {
Some(Value::String(t)) => match t.as_str() {
"record" => match parse_location {
RecordSchemaParseLocation::Root => {
self.parse_record(complex, enclosing_namespace)
}
RecordSchemaParseLocation::FromField => {
self.fetch_schema_ref(t, enclosing_namespace)
}
},
"enum" => self.parse_enum(complex, enclosing_namespace),
"array" => self.parse_array(complex, enclosing_namespace),
"map" => self.parse_map(complex, enclosing_namespace),
"fixed" => self.parse_fixed(complex, enclosing_namespace),
other => self.parse_known_schema(other, enclosing_namespace),
},
Some(Value::Object(data)) => {
self.parse_complex(data, enclosing_namespace, RecordSchemaParseLocation::Root)
}
Some(Value::Array(variants)) => self.parse_union(variants, enclosing_namespace),
Some(unknown) => Err(Details::GetComplexType(unknown.clone()).into()),
None => Err(Details::GetComplexTypeField.into()),
}
}
fn register_resolving_schema(&mut self, name: &Name, aliases: &Aliases) {
let resolving_schema = Schema::Ref { name: name.clone() };
self.resolving_schemas
.insert(name.clone(), resolving_schema.clone());
let namespace = &name.namespace;
if let Some(aliases) = aliases {
aliases.iter().for_each(|alias| {
let alias_fullname = alias.fully_qualified_name(namespace);
self.resolving_schemas
.insert(alias_fullname, resolving_schema.clone());
});
}
}
fn register_parsed_schema(
&mut self,
fully_qualified_name: &Name,
schema: &Schema,
aliases: &Aliases,
) {
// FIXME, this should be globally aware, so if there is something overwriting something
// else then there is an ambiguous schema definition. An appropriate error should be thrown
self.parsed_schemas
.insert(fully_qualified_name.clone(), schema.clone());
self.resolving_schemas.remove(fully_qualified_name);
let namespace = &fully_qualified_name.namespace;
if let Some(aliases) = aliases {
aliases.iter().for_each(|alias| {
let alias_fullname = alias.fully_qualified_name(namespace);
self.resolving_schemas.remove(&alias_fullname);
self.parsed_schemas.insert(alias_fullname, schema.clone());
});
}
}
/// Returns already parsed schema or a schema that is currently being resolved.
fn get_already_seen_schema(
&self,
complex: &Map<String, Value>,
enclosing_namespace: &Namespace,
) -> Option<&Schema> {
match complex.get("type") {
Some(Value::String(typ)) => {
let name = Name::new(typ.as_str())
.unwrap()
.fully_qualified_name(enclosing_namespace);
self.resolving_schemas
.get(&name)
.or_else(|| self.parsed_schemas.get(&name))
}
_ => None,
}
}
/// Parse a `serde_json::Value` representing an Avro record type into a `Schema`.
fn parse_record(
&mut self,
complex: &Map<String, Value>,
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
let fields_opt = complex.get("fields");
if fields_opt.is_none()
&& let Some(seen) = self.get_already_seen_schema(complex, enclosing_namespace)
{
return Ok(seen.clone());
}
let fully_qualified_name = Name::parse(complex, enclosing_namespace)?;
let aliases =
self.fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace);
let mut lookup = BTreeMap::new();
self.register_resolving_schema(&fully_qualified_name, &aliases);
debug!("Going to parse record schema: {:?}", &fully_qualified_name);
let fields: Vec<RecordField> = fields_opt
.and_then(|fields| fields.as_array())
.ok_or_else(|| Error::new(Details::GetRecordFieldsJson))
.and_then(|fields| {
fields
.iter()
.filter_map(|field| field.as_object())
.enumerate()
.map(|(position, field)| {
RecordField::parse(field, position, self, &fully_qualified_name)
})
.collect::<Result<_, _>>()
})?;
for field in &fields {
if let Some(_old) = lookup.insert(field.name.clone(), field.position) {
return Err(Details::FieldNameDuplicate(field.name.clone()).into());
}
if let Some(ref field_aliases) = field.aliases {
for alias in field_aliases {
lookup.insert(alias.clone(), field.position);
}
}
}
let schema = Schema::Record(RecordSchema {
name: fully_qualified_name.clone(),
aliases: aliases.clone(),
doc: complex.doc(),
fields,
lookup,
attributes: self.get_custom_attributes(complex, vec!["fields"]),
});
self.register_parsed_schema(&fully_qualified_name, &schema, &aliases);
Ok(schema)
}
fn get_custom_attributes(
&self,
complex: &Map<String, Value>,
excluded: Vec<&'static str>,
) -> BTreeMap<String, Value> {
let mut custom_attributes: BTreeMap<String, Value> = BTreeMap::new();
for (key, value) in complex {
match key.as_str() {
"type" | "name" | "namespace" | "doc" | "aliases" | "logicalType" => continue,
candidate if excluded.contains(&candidate) => continue,
_ => custom_attributes.insert(key.clone(), value.clone()),
};
}
custom_attributes
}
/// Parse a `serde_json::Value` representing a Avro enum type into a `Schema`.
fn parse_enum(
&mut self,
complex: &Map<String, Value>,
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
let symbols_opt = complex.get("symbols");
if symbols_opt.is_none()
&& let Some(seen) = self.get_already_seen_schema(complex, enclosing_namespace)
{
return Ok(seen.clone());
}
let fully_qualified_name = Name::parse(complex, enclosing_namespace)?;
let aliases =
self.fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace);
let symbols: Vec<String> = symbols_opt
.and_then(|v| v.as_array())
.ok_or_else(|| Error::from(Details::GetEnumSymbolsField))
.and_then(|symbols| {
symbols
.iter()
.map(|symbol| symbol.as_str().map(|s| s.to_string()))
.collect::<Option<_>>()
.ok_or_else(|| Error::from(Details::GetEnumSymbols))
})?;
let mut existing_symbols: HashSet<&String> = HashSet::with_capacity(symbols.len());
for symbol in symbols.iter() {
validate_enum_symbol_name(symbol)?;
// Ensure there are no duplicate symbols
if existing_symbols.contains(&symbol) {
return Err(Details::EnumSymbolDuplicate(symbol.to_string()).into());
}
existing_symbols.insert(symbol);
}
let mut default: Option<String> = None;
if let Some(value) = complex.get("default") {
if let Value::String(ref s) = *value {
default = Some(s.clone());
} else {
return Err(Details::EnumDefaultWrongType(value.clone()).into());
}
}
if let Some(ref value) = default {
let resolved = types::Value::from(value.clone())
.resolve_enum(&symbols, &Some(value.to_string()), &None)
.is_ok();
if !resolved {
return Err(Details::GetEnumDefault {
symbol: value.to_string(),
symbols,
}
.into());
}
}
let schema = Schema::Enum(EnumSchema {
name: fully_qualified_name.clone(),
aliases: aliases.clone(),
doc: complex.doc(),
symbols,
default,
attributes: self.get_custom_attributes(complex, vec!["symbols", "default"]),
});
self.register_parsed_schema(&fully_qualified_name, &schema, &aliases);
Ok(schema)
}
/// Parse a `serde_json::Value` representing a Avro array type into a `Schema`.
fn parse_array(
&mut self,
complex: &Map<String, Value>,
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
let items = complex
.get("items")
.ok_or_else(|| Details::GetArrayItemsField.into())
.and_then(|items| self.parse(items, enclosing_namespace))?;
let default = if let Some(default) = complex.get("default").cloned() {
if let Value::Array(_) = default {
let crate::types::Value::Array(array) = crate::types::Value::try_from(default)?
else {
unreachable!("JsonValue::Array can only become a Value::Array")
};
// Check that the default type matches the schema type
if let Some(value) = array.iter().find(|v| {
v.validate_internal(&items, &self.parsed_schemas, enclosing_namespace)
.is_some()
}) {
return Err(Details::ArrayDefaultWrongInnerType(items, value.clone()).into());
}
Some(array)
} else {
return Err(Details::ArrayDefaultWrongType(default).into());
}
} else {
None
};
Ok(Schema::Array(ArraySchema {
items: Box::new(items),
default,
attributes: self.get_custom_attributes(complex, vec!["items", "default"]),
}))
}
/// Parse a `serde_json::Value` representing a Avro map type into a `Schema`.
fn parse_map(
&mut self,
complex: &Map<String, Value>,
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
let types = complex
.get("values")
.ok_or_else(|| Details::GetMapValuesField.into())
.and_then(|types| self.parse(types, enclosing_namespace))?;
let default = if let Some(default) = complex.get("default").cloned() {
if let Value::Object(_) = default {
let crate::types::Value::Map(map) = crate::types::Value::try_from(default)? else {
unreachable!("JsonValue::Object can only become a Value::Map")
};
// Check that the default type matches the schema type
if let Some(value) = map.values().find(|v| {
v.validate_internal(&types, &self.parsed_schemas, enclosing_namespace)
.is_some()
}) {
return Err(Details::MapDefaultWrongInnerType(types, value.clone()).into());
}
Some(map)
} else {
return Err(Details::MapDefaultWrongType(default).into());
}
} else {
None
};
Ok(Schema::Map(MapSchema {
types: Box::new(types),
default,
attributes: self.get_custom_attributes(complex, vec!["values", "default"]),
}))
}
/// Parse a `serde_json::Value` representing a Avro union type into a `Schema`.
fn parse_union(
&mut self,
items: &[Value],
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
items
.iter()
.map(|v| self.parse(v, enclosing_namespace))
.collect::<Result<Vec<_>, _>>()
.and_then(|schemas| {
if schemas.is_empty() {
error!(
"Union schemas should have at least two members! \
Please enable debug logging to find out which Record schema \
declares the union with 'RUST_LOG=apache_avro::schema=debug'."
);
} else if schemas.len() == 1 {
warn!(
"Union schema with just one member! Consider dropping the union! \
Please enable debug logging to find out which Record schema \
declares the union with 'RUST_LOG=apache_avro::schema=debug'."
);
}
Ok(Schema::Union(UnionSchema::new(schemas)?))
})
}
/// Parse a `serde_json::Value` representing a Avro fixed type into a `Schema`.
fn parse_fixed(
&mut self,
complex: &Map<String, Value>,
enclosing_namespace: &Namespace,
) -> AvroResult<Schema> {
let size_opt = complex.get("size");
if size_opt.is_none()
&& let Some(seen) = self.get_already_seen_schema(complex, enclosing_namespace)
{
return Ok(seen.clone());
}
let doc = complex.get("doc").and_then(|v| match &v {
&Value::String(docstr) => Some(docstr.clone()),
_ => None,
});
let size = match size_opt {
Some(size) => size
.as_u64()
.ok_or_else(|| Details::GetFixedSizeFieldPositive(size.clone())),
None => Err(Details::GetFixedSizeField),
}?;
let fully_qualified_name = Name::parse(complex, enclosing_namespace)?;
let aliases =
self.fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace);
let schema = Schema::Fixed(FixedSchema {
name: fully_qualified_name.clone(),
aliases: aliases.clone(),
doc,
size: size as usize,
attributes: self.get_custom_attributes(complex, vec!["size"]),
});
self.register_parsed_schema(&fully_qualified_name, &schema, &aliases);
Ok(schema)
}
// A type alias may be specified either as a fully namespace-qualified, or relative
// to the namespace of the name it is an alias for. For example, if a type named "a.b"
// has aliases of "c" and "x.y", then the fully qualified names of its aliases are "a.c"
// and "x.y".
// https://avro.apache.org/docs/++version++/specification/#aliases
fn fix_aliases_namespace(
&self,
aliases: Option<Vec<String>>,
namespace: &Namespace,
) -> Aliases {
aliases.map(|aliases| {
aliases
.iter()
.map(|alias| {
if alias.find('.').is_none() {
match namespace {
Some(ns) => format!("{ns}.{alias}"),
None => alias.clone(),
}
} else {
alias.clone()
}
})
.map(|alias| Alias::new(alias.as_str()).unwrap())
.collect()
})
}
fn get_schema_type_name(&self, name: Name, value: Value) -> Name {
match value.get("type") {
Some(Value::Object(complex_type)) => match complex_type.name() {
Some(name) => Name::new(name.as_str()).unwrap(),
_ => name,
},
_ => name,
}
}
fn parse_json_integer_for_decimal(
&self,
value: &serde_json::Number,
) -> AvroResult<DecimalMetadata> {
Ok(if value.is_u64() {
let num = value
.as_u64()
.ok_or_else(|| Details::GetU64FromJson(value.clone()))?;
num.try_into()
.map_err(|e| Details::ConvertU64ToUsize(e, num))?
} else if value.is_i64() {
let num = value
.as_i64()
.ok_or_else(|| Details::GetI64FromJson(value.clone()))?;
num.try_into()
.map_err(|e| Details::ConvertI64ToUsize(e, num))?
} else {
return Err(Details::GetPrecisionOrScaleFromJson(value.clone()).into());
})
}
}