msgpack-rust icon indicating copy to clipboard operation
msgpack-rust copied to clipboard

Integer is not parsed as float in untagged enum

Open Kobzol opened this issue 6 years ago • 2 comments

Thanks to https://github.com/3Hren/msgpack-rust/pull/204, it is possible to parse integers as floats. However, this does not seem to work if the parsed type is nested inside an untagged enum.

For this MessagePack data: {b'heartbeat-interval': 1}

and this code:

use serde::{Deserialize};

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct MessageFloat {
    pub heartbeat_interval: f64,
}

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct MessageInt {
    pub heartbeat_interval: u64,
}

#[derive(Deserialize)]
#[serde(untagged)]
pub enum MessageWrapper<T> {
    Message(T),
}

fn main() {
    let data = std::fs::read("data.bin").unwrap();

    // parsing message is fine if the type is exact
    let msg: MessageInt = rmp_serde::from_slice(&data).unwrap();

    // parsing message is fine if the type is not exact
    let msg: MessageFloat = rmp_serde::from_slice(&data).unwrap();

    // parsing nested message is fine if the type is exact
    let msg: MessageWrapper<MessageInt> = rmp_serde::from_slice(&data).unwrap();

    // parsing nested message fails if the type is not exact
    let msg: MessageWrapper<MessageFloat> = rmp_serde::from_slice(&data).unwrap();
}

all parses work except the last one.

If you consider this to be a bug, let me know if I can help fixing this (point me to some API/file where I could start).

I have prepared a reproduction repository here: https://github.com/Kobzol/msgpack-nested-float-int The msgpack binary is in the root of the repository (data.bin).

Kobzol avatar Mar 24 '20 15:03 Kobzol

It'd be great if you could help with this. Have a look at https://github.com/3Hren/msgpack-rust/blob/3f25c4d26434f3e4fccc25dfcea38a72ecba812b/rmp-serde/src/decode.rs

Perhaps here? https://github.com/3Hren/msgpack-rust/blob/3f25c4d26434f3e4fccc25dfcea38a72ecba812b/rmp-serde/src/decode.rs#L616

or maybe it need special case instead of forward_to_deserialize_any

kornelski avatar Mar 24 '20 20:03 kornelski

So the difference seems to be that if the struct is deserialized directly, it calls deserialize_struct, which forwards to deserialize_f64, which can parse integers. But if there's an untagged enum, serde instead forwards to deserialize_any, which just parses a Map<str, u8>, returns that to serde and it fails because it expects Map<str, f64>.

I'm not sure how to work around this. It seems to be possible, because the same thing works in serde_json, however I checked it out and it seems to do number parsing in a different way than rmp.

Kobzol avatar Mar 24 '20 22:03 Kobzol