This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/avro-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new a50c8cd  feat!: Support `default` field for array and map (#467)
a50c8cd is described below

commit a50c8cdfa2dcab6eef0bf493fe98bb8184351faa
Author: Kriskras99 <[email protected]>
AuthorDate: Wed Feb 18 13:12:48 2026 +0100

    feat!: Support `default` field for array and map (#467)
    
    * feat!: Support `default` field for array and map
    
    * Actually assert tests and fix grammar
    
    Co-authored-by: Martin Grigorov <[email protected]>
    
    * fix: Support defaults with references
    
    * fix!: Replace `From` implementations that could panic with `TryFrom` 
implementations
    
    ---------
    
    Co-authored-by: default <[email protected]>
    Co-authored-by: Martin Grigorov <[email protected]>
---
 avro/src/error.rs               |  21 +-
 avro/src/schema/mod.rs          | 429 +++++++++++++++++++++++++++++++++-------
 avro/src/schema/parser.rs       |  77 ++++++--
 avro/src/schema/record/field.rs |   2 +-
 avro/src/types.rs               | 118 ++++++-----
 avro/src/writer.rs              |   4 +-
 avro/tests/schema.rs            |   2 +-
 7 files changed, 514 insertions(+), 139 deletions(-)

diff --git a/avro/src/error.rs b/avro/src/error.rs
index e2dfa28..17c7497 100644
--- a/avro/src/error.rs
+++ b/avro/src/error.rs
@@ -312,6 +312,9 @@ pub enum Details {
     #[error("`default`'s value type of field {0:?} in {1:?} must be {2:?}")]
     GetDefaultRecordField(String, String, String),
 
+    #[error("JSON number {0} could not be converted into an Avro value as it's 
too large")]
+    JsonNumberTooLarge(serde_json::Number),
+
     #[error("JSON value {0} claims to be u64 but cannot be converted")]
     GetU64FromJson(serde_json::Number),
 
@@ -343,6 +346,9 @@ pub enum Details {
     #[error("Cannot convert i32 to u128: {1}")]
     ConvertI32ToU128(#[source] std::num::TryFromIntError, i32),
 
+    #[error("Cannot convert usize to i64: {1}")]
+    ConvertUsizeToI64(#[source] std::num::TryFromIntError, usize),
+
     #[error("Invalid JSON value for decimal precision/scale integer: {0}")]
     GetPrecisionOrScaleFromJson(serde_json::Number),
 
@@ -431,9 +437,21 @@ pub enum Details {
     #[error("Duplicate enum symbol {0}")]
     EnumSymbolDuplicate(String),
 
-    #[error("Default value for enum must be a string! Got: {0}")]
+    #[error("Default value for an enum must be a string! Got: {0}")]
     EnumDefaultWrongType(serde_json::Value),
 
+    #[error("Default value for an array must be an array! Got: {0}")]
+    ArrayDefaultWrongType(serde_json::Value),
+
+    #[error("Default value for an array must be an array of {0}! Found: 
{1:?}")]
+    ArrayDefaultWrongInnerType(Schema, Value),
+
+    #[error("Default value for a map must be an object! Got: {0}")]
+    MapDefaultWrongType(serde_json::Value),
+
+    #[error("Default value for a map must be an object with (String, {0})! 
Found: (String, {1:?})")]
+    MapDefaultWrongInnerType(Schema, Value),
+
     #[error("No `items` in array")]
     GetArrayItemsField,
 
@@ -446,6 +464,7 @@ pub enum Details {
     #[error("Fixed schema has no `size`")]
     GetFixedSizeField,
 
+    #[deprecated(since = "0.22.0", note = "This error variant is not generated 
anymore")]
     #[error("Fixed schema's default value length ({0}) does not match its size 
({1})")]
     FixedDefaultLenSizeMismatch(usize, u64),
 
diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs
index 5d9e0ca..da5db6c 100644
--- a/avro/src/schema/mod.rs
+++ b/avro/src/schema/mod.rs
@@ -27,14 +27,15 @@ use crate::{
     AvroResult,
     error::{Details, Error},
     schema::{parser::Parser, record::RecordSchemaParseLocation},
-    schema_equality, types,
+    schema_equality,
+    types::{self, Value},
 };
 use digest::Digest;
 use serde::{
     Serialize, Serializer,
-    ser::{SerializeMap, SerializeSeq},
+    ser::{Error as _, SerializeMap, SerializeSeq},
 };
-use serde_json::{Map, Value};
+use serde_json::{Map, Value as JsonValue};
 use std::{
     collections::{BTreeMap, HashMap, HashSet},
     fmt,
@@ -166,13 +167,15 @@ pub enum Schema {
 #[derive(Clone, Debug, PartialEq)]
 pub struct MapSchema {
     pub types: Box<Schema>,
-    pub attributes: BTreeMap<String, Value>,
+    pub default: Option<HashMap<String, Value>>,
+    pub attributes: BTreeMap<String, JsonValue>,
 }
 
 #[derive(Clone, Debug, PartialEq)]
 pub struct ArraySchema {
     pub items: Box<Schema>,
-    pub attributes: BTreeMap<String, Value>,
+    pub default: Option<Vec<Value>>,
+    pub attributes: BTreeMap<String, JsonValue>,
 }
 
 impl PartialEq for Schema {
@@ -265,7 +268,7 @@ pub struct EnumSchema {
     pub default: Option<String>,
     /// The custom attributes of the schema
     #[builder(default = BTreeMap::new())]
-    pub attributes: BTreeMap<String, Value>,
+    pub attributes: BTreeMap<String, JsonValue>,
 }
 
 /// A description of a Fixed schema.
@@ -283,7 +286,7 @@ pub struct FixedSchema {
     pub size: usize,
     /// The custom attributes of the schema
     #[builder(default = BTreeMap::new())]
-    pub attributes: BTreeMap<String, Value>,
+    pub attributes: BTreeMap<String, JsonValue>,
 }
 
 impl FixedSchema {
@@ -459,12 +462,12 @@ impl Schema {
     pub fn parse_list(input: impl IntoIterator<Item = impl AsRef<str>>) -> 
AvroResult<Vec<Schema>> {
         let input = input.into_iter();
         let input_len = input.size_hint().0;
-        let mut input_schemas: HashMap<Name, Value> = 
HashMap::with_capacity(input_len);
+        let mut input_schemas: HashMap<Name, JsonValue> = 
HashMap::with_capacity(input_len);
         let mut input_order: Vec<Name> = Vec::with_capacity(input_len);
         for json in input {
             let json = json.as_ref();
-            let schema: Value = 
serde_json::from_str(json).map_err(Details::ParseSchemaJson)?;
-            if let Value::Object(inner) = &schema {
+            let schema: JsonValue = 
serde_json::from_str(json).map_err(Details::ParseSchemaJson)?;
+            if let JsonValue::Object(inner) = &schema {
                 let name = Name::parse(inner, &None)?;
                 let previous_value = input_schemas.insert(name.clone(), 
schema);
                 if previous_value.is_some() {
@@ -501,12 +504,12 @@ impl Schema {
     ) -> AvroResult<(Schema, Vec<Schema>)> {
         let schemata = schemata.into_iter();
         let schemata_len = schemata.size_hint().0;
-        let mut input_schemas: HashMap<Name, Value> = 
HashMap::with_capacity(schemata_len);
+        let mut input_schemas: HashMap<Name, JsonValue> = 
HashMap::with_capacity(schemata_len);
         let mut input_order: Vec<Name> = Vec::with_capacity(schemata_len);
         for json in schemata {
             let json = json.as_ref();
-            let schema: Value = 
serde_json::from_str(json).map_err(Details::ParseSchemaJson)?;
-            if let Value::Object(inner) = &schema {
+            let schema: JsonValue = 
serde_json::from_str(json).map_err(Details::ParseSchemaJson)?;
+            if let JsonValue::Object(inner) = &schema {
                 let name = Name::parse(inner, &None)?;
                 if let Some(_previous) = input_schemas.insert(name.clone(), 
schema) {
                     return 
Err(Details::NameCollision(name.fullname(None)).into());
@@ -539,20 +542,20 @@ impl Schema {
     }
 
     /// Parses an Avro schema from JSON.
-    pub fn parse(value: &Value) -> AvroResult<Schema> {
+    pub fn parse(value: &JsonValue) -> AvroResult<Schema> {
         let mut parser = Parser::default();
         parser.parse(value, &None)
     }
 
     /// Parses an Avro schema from JSON.
     /// Any `Schema::Ref`s must be known in the `names` map.
-    pub(crate) fn parse_with_names(value: &Value, names: Names) -> 
AvroResult<Schema> {
+    pub(crate) fn parse_with_names(value: &JsonValue, names: Names) -> 
AvroResult<Schema> {
         let mut parser = Parser::new(HashMap::with_capacity(1), 
Vec::with_capacity(1), names);
         parser.parse(value, &None)
     }
 
     /// Returns the custom attributes (metadata) if the schema supports them.
-    pub fn custom_attributes(&self) -> Option<&BTreeMap<String, Value>> {
+    pub fn custom_attributes(&self) -> Option<&BTreeMap<String, JsonValue>> {
         match self {
             Schema::Record(RecordSchema { attributes, .. })
             | Schema::Enum(EnumSchema { attributes, .. })
@@ -644,14 +647,16 @@ impl Schema {
     pub fn map(types: Schema) -> Self {
         Schema::Map(MapSchema {
             types: Box::new(types),
+            default: None,
             attributes: Default::default(),
         })
     }
 
     /// Returns a `Schema::Map` with the given types and custom attributes.
-    pub fn map_with_attributes(types: Schema, attributes: BTreeMap<String, 
Value>) -> Self {
+    pub fn map_with_attributes(types: Schema, attributes: BTreeMap<String, 
JsonValue>) -> Self {
         Schema::Map(MapSchema {
             types: Box::new(types),
+            default: None,
             attributes,
         })
     }
@@ -660,14 +665,16 @@ impl Schema {
     pub fn array(items: Schema) -> Self {
         Schema::Array(ArraySchema {
             items: Box::new(items),
+            default: None,
             attributes: Default::default(),
         })
     }
 
     /// Returns a `Schema::Array` with the given items and custom attributes.
-    pub fn array_with_attributes(items: Schema, attributes: BTreeMap<String, 
Value>) -> Self {
+    pub fn array_with_attributes(items: Schema, attributes: BTreeMap<String, 
JsonValue>) -> Self {
         Schema::Array(ArraySchema {
             items: Box::new(items),
+            default: None,
             attributes,
         })
     }
@@ -748,21 +755,43 @@ impl Serialize for Schema {
             Schema::Double => serializer.serialize_str("double"),
             Schema::Bytes => serializer.serialize_str("bytes"),
             Schema::String => serializer.serialize_str("string"),
-            Schema::Array(inner) => {
-                let mut map = serializer.serialize_map(Some(2 + 
inner.attributes.len()))?;
+            Schema::Array(ArraySchema {
+                items,
+                default,
+                attributes,
+            }) => {
+                let mut map = serializer.serialize_map(Some(
+                    2 + attributes.len() + if default.is_some() { 1 } else { 0 
},
+                ))?;
                 map.serialize_entry("type", "array")?;
-                map.serialize_entry("items", &*inner.items.clone())?;
-                for attr in &inner.attributes {
-                    map.serialize_entry(attr.0, attr.1)?;
+                map.serialize_entry("items", items)?;
+                if let Some(default) = default {
+                    let value = 
JsonValue::try_from(Value::Array(default.clone()))
+                        .map_err(S::Error::custom)?;
+                    map.serialize_entry("default", &value)?;
+                }
+                for (key, value) in attributes {
+                    map.serialize_entry(key, value)?;
                 }
                 map.end()
             }
-            Schema::Map(inner) => {
-                let mut map = serializer.serialize_map(Some(2 + 
inner.attributes.len()))?;
+            Schema::Map(MapSchema {
+                types,
+                default,
+                attributes,
+            }) => {
+                let mut map = serializer.serialize_map(Some(
+                    2 + attributes.len() + if default.is_some() { 1 } else { 0 
},
+                ))?;
                 map.serialize_entry("type", "map")?;
-                map.serialize_entry("values", &*inner.types.clone())?;
-                for attr in &inner.attributes {
-                    map.serialize_entry(attr.0, attr.1)?;
+                map.serialize_entry("values", types)?;
+                if let Some(default) = default {
+                    let value = 
JsonValue::try_from(Value::Map(default.clone()))
+                        .map_err(S::Error::custom)?;
+                    map.serialize_entry("default", &value)?;
+                }
+                for (key, value) in attributes {
+                    map.serialize_entry(key, value)?;
                 }
                 map.end()
             }
@@ -945,16 +974,16 @@ impl Serialize for Schema {
 /// Parses a valid Avro schema into [the Parsing Canonical Form].
 ///
 /// [the Parsing Canonical 
Form](https://avro.apache.org/docs/++version++/specification/#parsing-canonical-form-for-schemas)
-fn parsing_canonical_form(schema: &Value, defined_names: &mut HashSet<String>) 
-> String {
+fn parsing_canonical_form(schema: &JsonValue, defined_names: &mut 
HashSet<String>) -> String {
     match schema {
-        Value::Object(map) => pcf_map(map, defined_names),
-        Value::String(s) => pcf_string(s),
-        Value::Array(v) => pcf_array(v, defined_names),
+        JsonValue::Object(map) => pcf_map(map, defined_names),
+        JsonValue::String(s) => pcf_string(s),
+        JsonValue::Array(v) => pcf_array(v, defined_names),
         json => panic!("got invalid JSON value for canonical form of schema: 
{json}"),
     }
 }
 
-fn pcf_map(schema: &Map<String, Value>, defined_names: &mut HashSet<String>) 
-> String {
+fn pcf_map(schema: &Map<String, JsonValue>, defined_names: &mut 
HashSet<String>) -> String {
     let typ = schema.get("type").and_then(|v| v.as_str());
     let name = if is_named_type(typ) {
         let ns = schema.get("namespace").and_then(|v| v.as_str());
@@ -982,7 +1011,7 @@ fn pcf_map(schema: &Map<String, Value>, defined_names: 
&mut HashSet<String>) ->
         // Reduce primitive types to their simple form. ([PRIMITIVE] rule)
         if schema.len() == 1 && k == "type" {
             // Invariant: function is only callable from a valid schema, so 
this is acceptable.
-            if let Value::String(s) = v {
+            if let JsonValue::String(s) = v {
                 return pcf_string(s);
             }
         }
@@ -1043,7 +1072,7 @@ fn is_named_type(typ: Option<&str>) -> bool {
     )
 }
 
-fn pcf_array(arr: &[Value], defined_names: &mut HashSet<String>) -> String {
+fn pcf_array(arr: &[JsonValue], defined_names: &mut HashSet<String>) -> String 
{
     let inter = arr
         .iter()
         .map(|a| parsing_canonical_form(a, defined_names))
@@ -1426,7 +1455,7 @@ mod tests {
             fields: vec![RecordField {
                 name: "field_one".to_string(),
                 doc: None,
-                default: Some(Value::Null),
+                default: Some(JsonValue::Null),
                 aliases: None,
                 schema: Schema::Union(UnionSchema::new(vec![
                     Schema::Null,
@@ -1474,7 +1503,7 @@ mod tests {
                 RecordField {
                     name: "a".to_string(),
                     doc: None,
-                    default: Some(Value::Number(42i64.into())),
+                    default: Some(JsonValue::Number(42i64.into())),
                     aliases: None,
                     schema: Schema::Long,
                     order: RecordFieldOrder::Ascending,
@@ -2654,17 +2683,20 @@ mod tests {
         Ok(())
     }
 
-    fn expected_custom_attributes() -> BTreeMap<String, Value> {
-        let mut expected_attributes: BTreeMap<String, Value> = 
Default::default();
-        expected_attributes.insert("string_key".to_string(), 
Value::String("value".to_string()));
+    fn expected_custom_attributes() -> BTreeMap<String, JsonValue> {
+        let mut expected_attributes: BTreeMap<String, JsonValue> = 
Default::default();
+        expected_attributes.insert(
+            "string_key".to_string(),
+            JsonValue::String("value".to_string()),
+        );
         expected_attributes.insert("number_key".to_string(), json!(1.23));
-        expected_attributes.insert("null_key".to_string(), Value::Null);
+        expected_attributes.insert("null_key".to_string(), JsonValue::Null);
         expected_attributes.insert(
             "array_key".to_string(),
-            Value::Array(vec![json!(1), json!(2), json!(3)]),
+            JsonValue::Array(vec![json!(1), json!(2), json!(3)]),
         );
-        let mut object_value: HashMap<String, Value> = HashMap::new();
-        object_value.insert("key".to_string(), 
Value::String("value".to_string()));
+        let mut object_value: HashMap<String, JsonValue> = HashMap::new();
+        object_value.insert("key".to_string(), 
JsonValue::String("value".to_string()));
         expected_attributes.insert("object_key".to_string(), 
json!(object_value));
         expected_attributes
     }
@@ -2726,7 +2758,7 @@ mod tests {
                 assert_eq!(fields.len(), 1);
                 let field = &fields[0];
                 assert_eq!(&field.name, "a");
-                assert_eq!(&field.default, &Some(Value::Null));
+                assert_eq!(&field.default, &Some(JsonValue::Null));
                 match &field.schema {
                     Schema::Union(union) => {
                         assert_eq!(union.variants().len(), 2);
@@ -3107,7 +3139,7 @@ mod tests {
             Err(err) => {
                 assert_eq!(
                     err.to_string(),
-                    "Default value for enum must be a string! Got: 123"
+                    "Default value for an enum must be a string! Got: 123"
                 );
             }
             _ => panic!("Expected an error"),
@@ -3918,19 +3950,17 @@ mod tests {
             ]
         }
         "#;
-        let expected = Details::GetDefaultRecordField(
-            "f1".to_string(),
-            "ns.record1".to_string(),
-            r#"{"type":"array","items":"int"}"#.to_string(),
-        )
-        .to_string();
+
         let result = Schema::parse_str(schema_str);
         assert!(result.is_err());
         let err = result
             .map_err(|e| e.to_string())
             .err()
             .unwrap_or_else(|| "unexpected".to_string());
-        assert_eq!(expected, err);
+        assert_eq!(
+            r#"Default value for an array must be an array! Got: "invalid""#,
+            err
+        );
 
         Ok(())
     }
@@ -3952,19 +3982,17 @@ mod tests {
             ]
         }
         "#;
-        let expected = Details::GetDefaultRecordField(
-            "f1".to_string(),
-            "ns.record1".to_string(),
-            r#"{"type":"map","values":"string"}"#.to_string(),
-        )
-        .to_string();
+
         let result = Schema::parse_str(schema_str);
         assert!(result.is_err());
         let err = result
             .map_err(|e| e.to_string())
             .err()
             .unwrap_or_else(|| "unexpected".to_string());
-        assert_eq!(expected, err);
+        assert_eq!(
+            r#"Default value for a map must be an object! Got: "invalid""#,
+            err
+        );
 
         Ok(())
     }
@@ -4280,12 +4308,12 @@ mod tests {
         let attributes = BTreeMap::from([
             ("string_key".into(), "value".into()),
             ("number_key".into(), 1.23.into()),
-            ("null_key".into(), Value::Null),
+            ("null_key".into(), JsonValue::Null),
             (
                 "array_key".into(),
-                Value::Array(vec![1.into(), 2.into(), 3.into()]),
+                JsonValue::Array(vec![1.into(), 2.into(), 3.into()]),
             ),
-            ("object_key".into(), Value::Object(Map::default())),
+            ("object_key".into(), JsonValue::Object(Map::default())),
         ]);
 
         // Test serialize enum attributes
@@ -4701,7 +4729,7 @@ mod tests {
                 assert_eq!(field.name, "birthday");
                 assert_eq!(field.schema, Schema::Date);
                 assert_eq!(
-                    types::Value::from(field.default.clone().unwrap()),
+                    types::Value::try_from(field.default.clone().unwrap())?,
                     types::Value::Int(1681601653)
                 );
             }
@@ -4873,7 +4901,11 @@ mod tests {
 
         let schema1 = Schema::parse_str(raw_schema)?;
         match &schema1 {
-            Schema::Array(ArraySchema { items, attributes }) => {
+            Schema::Array(ArraySchema {
+                items,
+                default: _,
+                attributes,
+            }) => {
                 assert!(attributes.is_empty());
 
                 match **items {
@@ -4892,6 +4924,7 @@ mod tests {
                         match &fields[0].schema {
                             Schema::Array(ArraySchema {
                                 items: _,
+                                default: _,
                                 attributes,
                             }) => {
                                 assert!(attributes.is_empty());
@@ -4941,7 +4974,11 @@ mod tests {
 
         let schema1 = Schema::parse_str(raw_schema)?;
         match &schema1 {
-            Schema::Array(ArraySchema { items, attributes }) => {
+            Schema::Array(ArraySchema {
+                items,
+                default: _,
+                attributes,
+            }) => {
                 assert!(attributes.is_empty());
 
                 match **items {
@@ -4960,6 +4997,7 @@ mod tests {
                         match &fields[0].schema {
                             Schema::Map(MapSchema {
                                 types: _,
+                                default: _,
                                 attributes,
                             }) => {
                                 assert!(attributes.is_empty());
@@ -5147,7 +5185,7 @@ mod tests {
     }
 
     #[test]
-    fn avro_rs_460_enum_default_in_custom_attributes() -> TestResult {
+    fn avro_rs_460_enum_default_not_in_custom_attributes() -> TestResult {
         let schema = Schema::parse_str(
             r#"{
             "name": "enum_with_default",
@@ -5171,4 +5209,261 @@ mod tests {
 
         Ok(())
     }
+
+    #[test]
+    fn avro_rs_467_array_default() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "array",
+            "items": "string",
+            "default": []
+        }"#,
+        )?;
+
+        let Schema::Array(array) = schema else {
+            panic!("Expected Schema::Array, got {schema:?}");
+        };
+
+        assert_eq!(array.attributes, BTreeMap::new());
+        assert_eq!(array.default, Some(Vec::new()));
+
+        let json = serde_json::to_string(&Schema::Array(array))?;
+        assert!(json.contains(r#""default":[]"#));
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_467_array_default_with_actual_values() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "array",
+            "items": "string",
+            "default": ["foo", "bar"]
+        }"#,
+        )?;
+
+        let Schema::Array(array) = schema else {
+            panic!("Expected Schema::Array, got {schema:?}");
+        };
+
+        assert_eq!(array.attributes, BTreeMap::new());
+        assert_eq!(
+            array.default,
+            Some(vec![
+                Value::String("foo".into()),
+                Value::String("bar".into())
+            ])
+        );
+
+        let json = serde_json::to_string(&Schema::Array(array))?;
+        assert!(json.contains(r#""default":["foo","bar"]"#));
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_467_array_default_with_invalid_values() -> TestResult {
+        let err = Schema::parse_str(
+            r#"{
+            "type": "array",
+            "items": "string",
+            "default": [false, true]
+        }"#,
+        )
+        .unwrap_err();
+
+        assert_eq!(
+            err.to_string(),
+            "Default value for an array must be an array of String! Found: 
Boolean(false)"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_467_array_default_with_mixed_values() -> TestResult {
+        let err = Schema::parse_str(
+            r#"{
+            "type": "array",
+            "items": "string",
+            "default": ["foo", true]
+        }"#,
+        )
+        .unwrap_err();
+
+        assert_eq!(
+            err.to_string(),
+            "Default value for an array must be an array of String! Found: 
Boolean(true)"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_467_array_default_with_reference() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "record",
+            "name": "Something",
+            "fields": [
+                {
+                    "name": "one",
+                    "type": "enum",
+                    "name": "ABC",
+                    "symbols": ["A", "B", "C"]
+                },
+                {
+                    "name": "two",
+                    "type": "array",
+                    "items": "ABC",
+                    "default": ["A", "B", "C"]
+                }
+            ]
+        }"#,
+        )?;
+
+        let Schema::Record(record) = schema else {
+            panic!("Expected Schema::Record, got {schema:?}");
+        };
+        let Schema::Array(array) = &record.fields[1].schema else {
+            panic!("Expected Schema::Array, got {:?}", 
record.fields[1].schema);
+        };
+
+        assert_eq!(array.attributes, BTreeMap::new());
+        assert_eq!(
+            array.default,
+            Some(vec![
+                Value::String("A".into()),
+                Value::String("B".into()),
+                Value::String("C".into())
+            ])
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_467_map_default() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "map",
+            "values": "string",
+            "default": {}
+        }"#,
+        )?;
+
+        let Schema::Map(map) = schema else {
+            panic!("Expected Schema::Map, got {schema:?}");
+        };
+
+        assert_eq!(map.attributes, BTreeMap::new());
+        assert_eq!(map.default, Some(HashMap::new()));
+
+        let json = serde_json::to_string(&Schema::Map(map))?;
+        assert!(json.contains(r#""default":{}"#));
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_467_map_default_with_actual_values() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "map",
+            "values": "string",
+            "default": {"foo": "bar"}
+        }"#,
+        )?;
+
+        let Schema::Map(map) = schema else {
+            panic!("Expected Schema::Map, got {schema:?}");
+        };
+
+        let mut hashmap = HashMap::new();
+        hashmap.insert("foo".to_string(), Value::String("bar".into()));
+        assert_eq!(map.attributes, BTreeMap::new());
+        assert_eq!(map.default, Some(hashmap));
+
+        let json = serde_json::to_string(&Schema::Map(map))?;
+        assert!(json.contains(r#""default":{"foo":"bar"}"#));
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_467_map_default_with_invalid_values() -> TestResult {
+        let err = Schema::parse_str(
+            r#"{
+            "type": "map",
+            "values": "string",
+            "default": {"foo": true}
+        }"#,
+        )
+        .unwrap_err();
+
+        assert_eq!(
+            err.to_string(),
+            "Default value for a map must be an object with (String, String)! 
Found: (String, Boolean(true))"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_467_map_default_with_mixed_values() -> TestResult {
+        let err = Schema::parse_str(
+            r#"{
+            "type": "map",
+            "values": "string",
+            "default": {"foo": "bar", "spam": true}
+        }"#,
+        )
+        .unwrap_err();
+
+        assert_eq!(
+            err.to_string(),
+            "Default value for a map must be an object with (String, String)! 
Found: (String, Boolean(true))"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn avro_rs_467_map_default_with_reference() -> TestResult {
+        let schema = Schema::parse_str(
+            r#"{
+            "type": "record",
+            "name": "Something",
+            "fields": [
+                {
+                    "name": "one",
+                    "type": "enum",
+                    "name": "ABC",
+                    "symbols": ["A", "B", "C"]
+                },
+                {
+                    "name": "two",
+                    "type": "map",
+                    "values": "ABC",
+                    "default": {"foo": "A"}
+                }
+            ]
+        }"#,
+        )?;
+
+        let Schema::Record(record) = schema else {
+            panic!("Expected Schema::Record, got {schema:?}");
+        };
+        let Schema::Map(map) = &record.fields[1].schema else {
+            panic!("Expected Schema::Map, got {:?}", record.fields[1].schema);
+        };
+
+        let mut hashmap = HashMap::new();
+        hashmap.insert("foo".to_string(), Value::String("A".into()));
+        assert_eq!(map.attributes, BTreeMap::new());
+        assert_eq!(map.default, Some(hashmap));
+
+        Ok(())
+    }
 }
diff --git a/avro/src/schema/parser.rs b/avro/src/schema/parser.rs
index 9500b82..6747d84 100644
--- a/avro/src/schema/parser.rs
+++ b/avro/src/schema/parser.rs
@@ -18,9 +18,9 @@
 use crate::error::Details;
 use crate::schema::record::RecordSchemaParseLocation;
 use crate::schema::{
-    Alias, Aliases, DecimalMetadata, DecimalSchema, EnumSchema, FixedSchema, 
Name, Names,
-    Namespace, Precision, RecordField, RecordSchema, Scale, Schema, 
SchemaKind, UnionSchema,
-    UuidSchema,
+    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;
@@ -704,16 +704,35 @@ impl Parser {
         complex: &Map<String, Value>,
         enclosing_namespace: &Namespace,
     ) -> AvroResult<Schema> {
-        complex
+        let items = complex
             .get("items")
             .ok_or_else(|| Details::GetArrayItemsField.into())
-            .and_then(|items| self.parse(items, enclosing_namespace))
-            .map(|items| {
-                Schema::array_with_attributes(
-                    items,
-                    self.get_custom_attributes(complex, vec!["items"]),
-                )
-            })
+            .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`.
@@ -722,16 +741,36 @@ impl Parser {
         complex: &Map<String, Value>,
         enclosing_namespace: &Namespace,
     ) -> AvroResult<Schema> {
-        complex
+        let types = complex
             .get("values")
             .ok_or_else(|| Details::GetMapValuesField.into())
-            .and_then(|items| self.parse(items, enclosing_namespace))
-            .map(|items| {
-                Schema::map_with_attributes(
-                    items,
-                    self.get_custom_attributes(complex, vec!["values"]),
-                )
-            })
+            .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`.
diff --git a/avro/src/schema/record/field.rs b/avro/src/schema/record/field.rs
index 6e70cba..bda8a56 100644
--- a/avro/src/schema/record/field.rs
+++ b/avro/src/schema/record/field.rs
@@ -133,7 +133,7 @@ impl RecordField {
         default: &Option<Value>,
     ) -> AvroResult<()> {
         if let Some(value) = default {
-            let avro_value = types::Value::from(value.clone());
+            let avro_value = types::Value::try_from(value.clone())?;
             match field_schema {
                 Schema::Union(union_schema) => {
                     let schemas = &union_schema.schemas;
diff --git a/avro/src/types.rs b/avro/src/types.rs
index d070fe5..9c1f05f 100644
--- a/avro/src/types.rs
+++ b/avro/src/types.rs
@@ -156,11 +156,13 @@ impl From<()> for Value {
     }
 }
 
-impl From<usize> for Value {
-    fn from(value: usize) -> Self {
-        i64::try_from(value)
-            .expect("cannot convert usize to i64")
-            .into()
+impl TryFrom<usize> for Value {
+    type Error = Error;
+
+    fn try_from(value: usize) -> Result<Self, Self::Error> {
+        Ok(i64::try_from(value)
+            .map_err(|e| Details::ConvertUsizeToI64(e, value))?
+            .into())
     }
 }
 
@@ -269,29 +271,38 @@ impl<'a> From<Record<'a>> for Value {
     }
 }
 
-impl From<JsonValue> for Value {
-    fn from(value: JsonValue) -> Self {
+impl TryFrom<JsonValue> for Value {
+    type Error = Error;
+
+    fn try_from(value: JsonValue) -> Result<Self, Self::Error> {
         match value {
-            JsonValue::Null => Self::Null,
-            JsonValue::Bool(b) => b.into(),
+            JsonValue::Null => Ok(Self::Null),
+            JsonValue::Bool(b) => Ok(b.into()),
             JsonValue::Number(ref n) if n.is_i64() => {
                 let n = n.as_i64().unwrap();
                 if n >= i32::MIN as i64 && n <= i32::MAX as i64 {
-                    Value::Int(n as i32)
+                    Ok(Value::Int(n as i32))
                 } else {
-                    Value::Long(n)
+                    Ok(Value::Long(n))
                 }
             }
-            JsonValue::Number(ref n) if n.is_f64() => 
Value::Double(n.as_f64().unwrap()),
-            JsonValue::Number(n) => panic!("{n:?} does not fit into an Avro 
long"),
-            JsonValue::String(s) => s.into(),
-            JsonValue::Array(items) => 
Value::Array(items.into_iter().map(Value::from).collect()),
-            JsonValue::Object(items) => Value::Map(
-                items
+            JsonValue::Number(ref n) if n.is_f64() => 
Ok(Value::Double(n.as_f64().unwrap())),
+            JsonValue::Number(n) => Err(Details::JsonNumberTooLarge(n).into()),
+            JsonValue::String(s) => Ok(s.into()),
+            JsonValue::Array(items) => {
+                let items = items
                     .into_iter()
-                    .map(|(key, value)| (key, value.into()))
-                    .collect(),
-            ),
+                    .map(Value::try_from)
+                    .collect::<Result<Vec<_>, _>>()?;
+                Ok(Value::Array(items))
+            }
+            JsonValue::Object(items) => {
+                let items = items
+                    .into_iter()
+                    .map(|(key, value)| Value::try_from(value).map(|v| (key, 
v)))
+                    .collect::<Result<HashMap<_, _>, _>>()?;
+                Ok(Value::Map(items))
+            }
         }
     }
 }
@@ -1161,7 +1172,7 @@ impl Value {
                                 ref symbols,
                                 ref default,
                                 ..
-                            }) => Value::from(value.clone()).resolve_enum(
+                            }) => Value::try_from(value.clone())?.resolve_enum(
                                 symbols,
                                 default,
                                 &field.default.clone(),
@@ -1174,16 +1185,18 @@ impl Value {
                                     Schema::Null => Value::Union(0, 
Box::new(Value::Null)),
                                     _ => Value::Union(
                                         0,
-                                        
Box::new(Value::from(value.clone()).resolve_internal(
-                                            first,
-                                            names,
-                                            enclosing_namespace,
-                                            &field.default,
-                                        )?),
+                                        Box::new(
+                                            
Value::try_from(value.clone())?.resolve_internal(
+                                                first,
+                                                names,
+                                                enclosing_namespace,
+                                                &field.default,
+                                            )?,
+                                        ),
                                     ),
                                 }
                             }
-                            _ => Value::from(value.clone()),
+                            _ => Value::try_from(value.clone())?,
                         },
                         None => {
                             return 
Err(Details::GetField(field.name.clone()).into());
@@ -1346,7 +1359,7 @@ mod tests {
                 Value::Array(vec![Value::Boolean(true)]),
                 Schema::array(Schema::Long),
                 false,
-                "Invalid value: Array([Boolean(true)]) for schema: 
Array(ArraySchema { items: Long, attributes: {} }). Reason: Unsupported 
value-schema combination! Value: Boolean(true), schema: Long",
+                "Invalid value: Array([Boolean(true)]) for schema: 
Array(ArraySchema { items: Long, default: None, attributes: {} }). Reason: 
Unsupported value-schema combination! Value: Boolean(true), schema: Long",
             ),
             (
                 Value::Record(vec![]),
@@ -3073,7 +3086,7 @@ Field with name '"b"' is not a member of the map items"#,
         "#,
         )?;
 
-        let avro_value = Value::from(value);
+        let avro_value = Value::try_from(value)?;
 
         let schemas = Schema::parse_list([main_schema, referenced_schema])?;
 
@@ -3113,7 +3126,7 @@ Field with name '"b"' is not a member of the map items"#,
         "#,
         )?;
 
-        let avro_value = Value::from(value);
+        let avro_value = Value::try_from(value)?;
 
         let schemata = Schema::parse_list([referenced_enum, referenced_record, 
main_schema])?;
 
@@ -3212,30 +3225,33 @@ Field with name '"b"' is not a member of the map 
items"#,
     }
 
     #[test]
-    fn avro_3928_from_serde_value_to_types_value() {
-        assert_eq!(Value::from(serde_json::Value::Null), Value::Null);
-        assert_eq!(Value::from(json!(true)), Value::Boolean(true));
-        assert_eq!(Value::from(json!(false)), Value::Boolean(false));
-        assert_eq!(Value::from(json!(0)), Value::Int(0));
-        assert_eq!(Value::from(json!(i32::MIN)), Value::Int(i32::MIN));
-        assert_eq!(Value::from(json!(i32::MAX)), Value::Int(i32::MAX));
+    fn avro_3928_from_serde_value_to_types_value() -> TestResult {
+        assert_eq!(Value::try_from(serde_json::Value::Null)?, Value::Null);
+        assert_eq!(Value::try_from(json!(true))?, Value::Boolean(true));
+        assert_eq!(Value::try_from(json!(false))?, Value::Boolean(false));
+        assert_eq!(Value::try_from(json!(0))?, Value::Int(0));
+        assert_eq!(Value::try_from(json!(i32::MIN))?, Value::Int(i32::MIN));
+        assert_eq!(Value::try_from(json!(i32::MAX))?, Value::Int(i32::MAX));
         assert_eq!(
-            Value::from(json!(i32::MIN as i64 - 1)),
+            Value::try_from(json!(i32::MIN as i64 - 1))?,
             Value::Long(i32::MIN as i64 - 1)
         );
         assert_eq!(
-            Value::from(json!(i32::MAX as i64 + 1)),
+            Value::try_from(json!(i32::MAX as i64 + 1))?,
             Value::Long(i32::MAX as i64 + 1)
         );
-        assert_eq!(Value::from(json!(1.23)), Value::Double(1.23));
-        assert_eq!(Value::from(json!(-1.23)), Value::Double(-1.23));
-        assert_eq!(Value::from(json!(u64::MIN)), Value::Int(u64::MIN as i32));
+        assert_eq!(Value::try_from(json!(1.23))?, Value::Double(1.23));
+        assert_eq!(Value::try_from(json!(-1.23))?, Value::Double(-1.23));
+        assert_eq!(
+            Value::try_from(json!(u64::MIN))?,
+            Value::Int(u64::MIN as i32)
+        );
         assert_eq!(
-            Value::from(json!("some text")),
+            Value::try_from(json!("some text"))?,
             Value::String("some text".into())
         );
         assert_eq!(
-            Value::from(json!(["text1", "text2", "text3"])),
+            Value::try_from(json!(["text1", "text2", "text3"]))?,
             Value::Array(vec![
                 Value::String("text1".into()),
                 Value::String("text2".into()),
@@ -3243,7 +3259,7 @@ Field with name '"b"' is not a member of the map items"#,
             ])
         );
         assert_eq!(
-            Value::from(json!({"key1": "value1", "key2": "value2"})),
+            Value::try_from(json!({"key1": "value1", "key2": "value2"}))?,
             Value::Map(
                 vec![
                     ("key1".into(), Value::String("value1".into())),
@@ -3253,6 +3269,7 @@ Field with name '"b"' is not a member of the map items"#,
                 .collect()
             )
         );
+        Ok(())
     }
 
     #[test]
@@ -3491,8 +3508,13 @@ Field with name '"b"' is not a member of the map items"#,
     }
 
     #[test]
-    #[should_panic(expected = "Number(18446744073709551615) does not fit into 
an Avro long")]
     fn avro_rs_450_serde_json_number_u64_max() {
-        let _ = Value::from(json!(u64::MAX));
+        assert_eq!(
+            Value::try_from(json!(u64::MAX))
+                .unwrap_err()
+                .into_details()
+                .to_string(),
+            "JSON number 18446744073709551615 could not be converted into an 
Avro value as it's too large"
+        );
     }
 }
diff --git a/avro/src/writer.rs b/avro/src/writer.rs
index d75dccb..c8e02c8 100644
--- a/avro/src/writer.rs
+++ b/avro/src/writer.rs
@@ -390,8 +390,8 @@ impl<'a, W: Write> Writer<'a, W> {
         let num_values = self.num_values;
         let stream_len = self.buffer.len();
 
-        num_bytes += self.append_raw(&num_values.into(), &Schema::Long)?
-            + self.append_raw(&stream_len.into(), &Schema::Long)?
+        num_bytes += self.append_raw(&num_values.try_into()?, &Schema::Long)?
+            + self.append_raw(&stream_len.try_into()?, &Schema::Long)?
             + self
                 .writer
                 .write(self.buffer.as_ref())
diff --git a/avro/tests/schema.rs b/avro/tests/schema.rs
index 04f0d25..667f056 100644
--- a/avro/tests/schema.rs
+++ b/avro/tests/schema.rs
@@ -2351,7 +2351,7 @@ fn avro_rs_181_single_null_record() -> TestResult {
     let mut buff = Cursor::new(Vec::new());
     let schema = Schema::parse_str(r#""null""#)?;
     let mut writer = Writer::new(&schema, &mut buff)?;
-    writer.append_value(serde_json::Value::Null)?;
+    writer.append_value(Value::Null)?;
     writer.into_inner()?;
     buff.set_position(0);
 


Reply via email to