Anupam
Anupam

Reputation: 538

How to handle multiple enums from Binary data type in single match statement in Rust

My main objective is to write the buy() function here. The function can receive either Cars or Bike enum in a Binary format.

Following is my code:

use cosmwasm_std::{from_binary, to_binary, Binary};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
enum Cars {
    Audi { id: u32, number_plate: u32 },
    Bmw { id: u32, number_plate: u32 },
}

#[derive(Serialize, Deserialize)]
enum Bikes {
    Yamaha { id: u32, number_plate: u32 },
    Bajaj { id: u32, number_plate: u32 },
}

fn buy(prop: Binary) {
    match from_binary(&prop).unwrap() {
        Cars::Audi { id, number_plate } => {
            println!("{:?}", (id, number_plate));
        }
        _ => {}
    }
    match from_binary(&prop).unwrap() {
        Bikes::Yamaha { id, number_plate } => {
            println!("{:?}", (id, number_plate));
        }
        _ => {}
    }
}

fn main() {
    let x = Cars::Audi {
        id: 0,
        number_plate: 1,
    };
    let y = Bikes::Yamaha {
        id: 0,
        number_plate: 1,
    };

    buy(to_binary(&x).unwrap());
    buy(to_binary(&y).unwrap());
}

When I am trying to do it this way, its giving me this error:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseErr 
{ target_type: "some_testing::Bikes", msg: "unknown variant `Audi`, expected 
`Yamaha` or `Bajaj`" }', src/main.rs:23:30

What is the appropriate way of doing this?

Upvotes: 0

Views: 203

Answers (2)

peterulb
peterulb

Reputation: 2988

Usually you would go with serdes Untagged enum (see documentation). You can see that it works in buy_json.
However in this library it fails with internal error: entered unreachable code. I've checked the code and found the following statement:

/// Unsupported. We rely on typed deserialization methods, even if a JSON
/// has enough type information to detect types in many cases.
///
/// See https://serde.rs/impl-deserialize.html to learn more about the differentiation
/// between deserialize_{type} and deserialize_any.

You can workaround that with something similar to buy_workaround.

use cosmwasm_std::{from_binary, to_binary, Binary, StdError};
use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string};

#[derive(Serialize, Deserialize)]
enum Cars {
    Audi { id: u32, number_plate: u32 },
    Bmw { id: u32, number_plate: u32 },
}

#[derive(Serialize, Deserialize)]
enum Bikes {
    Yamaha { id: u32, number_plate: u32 },
    Bajaj { id: u32, number_plate: u32 },
}

#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum Wrappy {
    Car(Cars),
    Bike(Bikes),
}

fn buy(prop: Binary) {
    match from_binary::<Wrappy>(&prop) {
        Ok(ok) => match ok {
            Wrappy::Car(car) => match car {
                Cars::Audi { id, number_plate } => println!("Binary Audi {:?}", (id, number_plate)),
                _ => {
                    println!("Other car")
                }
            },
            Wrappy::Bike(bike) => match bike {
                Bikes::Yamaha { id, number_plate } => {
                    println!("Binary Yamaha {:?}", (id, number_plate))
                }
                _ => {
                    println!("Other bike")
                }
            },
        },
        Err(e) => println!("{:?}", e),
    }
}

fn buy_workaround(prop: Binary) {
    if let Some(ok) = get_wrapper(&prop) {
        match ok {
            Wrappy::Car(car) => match car {
                Cars::Audi { id, number_plate } => println!("Binary Audi {:?}", (id, number_plate)),
                _ => {
                    println!("Other car")
                }
            },
            Wrappy::Bike(bike) => match bike {
                Bikes::Yamaha { id, number_plate } => {
                    println!("Binary Yamaha {:?}", (id, number_plate))
                }
                _ => {
                    println!("Other bike")
                }
            },
        }
    }
}

fn get_wrapper(prop: &Binary) -> Option<Wrappy> {
    match from_binary::<Cars>(prop) {
        Ok(car) => Some(Wrappy::Car(car)),
        Err(StdError::ParseErr { .. }) => match from_binary::<Bikes>(prop) {
            Ok(bike) => Some(Wrappy::Bike(bike)),
            Err(_) => None,
        },
        _ => None,
    }
}

fn buy_json(prop: &str) {
    match from_str::<Wrappy>(prop) {
        Ok(ok) => match ok {
            Wrappy::Car(car) => match car {
                Cars::Audi { id, number_plate } => println!("Json Audi {:?}", (id, number_plate)),
                _ => {
                    println!("Other car")
                }
            },
            Wrappy::Bike(bike) => match bike {
                Bikes::Yamaha { id, number_plate } => {
                    println!("Json Yamaha {:?}", (id, number_plate))
                }
                _ => {
                    println!("Other bike")
                }
            },
        },
        Err(e) => println!("{:?}", e),
    }
}

fn main() {
    let x = Cars::Audi {
        id: 0,
        number_plate: 1,
    };
    let y = Bikes::Yamaha {
        id: 0,
        number_plate: 1,
    };

    let json = to_string(&x).unwrap();
    println!("{}", json);

    buy_json(&json);
    buy_json(&to_string(&y).unwrap());

    println!(
        "Binary format: {:?}",
        String::from_utf8_lossy(to_binary(&x).unwrap().as_slice())
    );

    buy_workaround(to_binary(&x).unwrap());
    buy_workaround(to_binary(&y).unwrap());

    buy(to_binary(&x).unwrap());
    buy(to_binary(&y).unwrap());
}

Upvotes: 0

Locke
Locke

Reputation: 8934

The easiest solution would be to just merge the two traits to sidestep the issue entirely. However, I'm guessing that may not be an option for you or it would not make much conceptual sense for the types.

The way to resolve your issue is to match Ok(_) as well as the enum variable instead of unwrapping. Unwrapping will cause it to panic if it was unable to parse the data to one of your enum variants. You still will need to have two if statements though since the two values being parsed are unrelated types. Here I use if let statements instead of match statements to make it a little more concise since it only matched a single variant.

fn buy(prop: Binary) {
    if let Ok(Cars::Audi { id, number_plate }) = from_binary(&prop) {
        println!("{:?}", (id, number_plate));
    }

    if let Ok(Bikes::Yamaha { id, number_plate }) = from_binary(&prop) {
        println!("{:?}", (id, number_plate));
    }
}

Upvotes: 2

Related Questions