flavray / avro-rs

Avro client library implementation in Rust
MIT License
169 stars 95 forks source link

Unions can't have records? #190

Open kitsuneninetails opened 3 years ago

kitsuneninetails commented 3 years ago

As far as I know, the following schema is perfectly acceptable in avro:

{
  "type": "record",
  "name": "Test",
  "fields": [
    {
      "name": "event",
      "type": [
        "null", 
        {
          "type": "record",
          "name": "Updated",
          "fields": [
            {
              "name": "id",
              "type": "string"
            }, 
          ]
        }
      ]
    }

But, the fact that I have a union where one type is not a primitive type (i.e. a record), seems to return "unsupported Union" errors when trying to deserialize (it builds the schema just fine, apparently).

Looking at the code, it indeed has:


            Value::Union(u) => match **u {
                Value::Null => visitor.visit_unit(),
                Value::Boolean(b) => visitor.visit_bool(b),
                Value::Int(i) => visitor.visit_i32(i),
                Value::Long(i) => visitor.visit_i64(i),
                Value::Float(f) => visitor.visit_f32(f),
                Value::Double(d) => visitor.visit_f64(d),
                _ => Err(de::Error::custom("Unsupported union")),
            },

in the deserialize code (deserialize_any).

Why isn't this supported? Wouldn't it be easy to just have the Record type run a recursive parser on the body underneath?

Edit:

I found some code in deserialize struct that has:

        match *self.input {
            Value::Record(ref fields) => visitor.visit_map(StructDeserializer::new(fields)),
            Value::Union(ref inner) => match **inner {
                Value::Record(ref fields) => visitor.visit_map(StructDeserializer::new(fields)),
                _ => Err(de::Error::custom("not a record")),
            },
            _ => Err(de::Error::custom("not a record")),
        }

Is this saying that I have to deserialize unions with records straight into a struct (or, I assume, an Option to handle unions with "null"?)? So, basically, skip the enum in the deserialized data structure and go straight to the one variant that matches the struct?

lerouxrgd commented 3 years ago

Just FYI this should match your schema:

#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct Updated {
    pub id: String,
}

impl Default for Updated {
    fn default() -> Updated {
        Updated {
            id: String::default(),
        }
    }
}

#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct Test {
    pub event: Option<Updated>,
}

impl Default for Test {
    fn default() -> Test {
        Test {
            event: None,
        }
    }
}