How to use Serde to parse a field that might fail to be deserialized without failing the entire deserialization? - error-handling

I am deserializing some JSON objects which come in as requests. The input body is nested, but a certain field is sometimes misformatted for a variety of reasons. In that situation I still want the rest of the object. This doesn't all have to be done through serde; but what is happening now, is that if a single subfield is messed up, the whole request is trashed. I want to somehow still deserialize that result and just mark the field as errored out. How can this be done?
E.g. the data schema might look like:
struct BigNested {
a: Vec<A>,
b: B, // definition omitted
}
struct A {
keep_this: Foo,
trouble: SometimesBad,
}
trouble is the field that's frequently coming in messed up. I would be happy to (e.g.) turn trouble into a Result<SometimesBad, Whatever> and process it from there, but I don't know how to get serde to let me do that.

certain field is sometimes misformatted
You didn't say how malformed the incoming JSON was. Assuming it's still valid JSON, you can pull this off with Serde's struct flatten and customized deserialization:
The customized deserialization is done in a way that never fails for valid JSON input, although it may not return value of expected type if the input has unexpected format.
But these unexpected fields still need to go somewhere. Serde's struct flatten comes in handy here to catch them since any JSON snippet can be deserialized to a HashMap<String, Value>.
//# serde = { version = "1.0.103", features = ["derive"] }
//# serde_json = "1.0.44"
use serde::{Deserialize, Deserializer, de::DeserializeOwned};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Deserialize, Debug)]
struct A {
keep_this: Foo,
trouble: SometimesBad,
}
#[derive(Deserialize, Debug)]
struct Foo {
foo: i32,
}
#[derive(Deserialize, Debug)]
struct SometimesBad {
inner: TryParse<Bar>,
#[serde(flatten)]
blackhole: HashMap<String, Value>,
}
#[derive(Deserialize, Debug)]
struct Bar {
bar: String,
}
#[derive(Debug)]
enum TryParse<T> {
Parsed(T),
Unparsed(Value),
NotPresent
}
impl<'de, T: DeserializeOwned> Deserialize<'de> for TryParse<T> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
match Option::<Value>::deserialize(deserializer)? {
None => Ok(TryParse::NotPresent),
Some(value) => match T::deserialize(&value) {
Ok(t) => Ok(TryParse::Parsed(t)),
Err(_) => Ok(TryParse::Unparsed(value)),
},
}
}
}
fn main() {
let valid = r#"{ "keep_this": { "foo": 1 }, "trouble": { "inner": { "bar": "one"}}}"#;
println!("{:#?}", serde_json::from_str::<A>(valid));
let extra_field = r#"{ "keep_this": { "foo": 1 }, "trouble": { "inner": { "bar": "one"}, "extra": 2019}}"#;
println!("{:#?}", serde_json::from_str::<A>(extra_field));
let wrong_type = r#"{ "keep_this": { "foo": 1 }, "trouble": { "inner": { "bar": 1}}}"#;
println!("{:#?}", serde_json::from_str::<A>(wrong_type));
let missing_field = r#"{ "keep_this": { "foo": 1 }, "trouble": { "inner": { "baz": "one"}}}"#;
println!("{:#?}", serde_json::from_str::<A>(missing_field));
let missing_inner = r#"{ "keep_this": { "foo": 1 }, "trouble": { "whatever": { "bar": "one"}}}"#;
println!("{:#?}", serde_json::from_str::<A>(missing_inner));
}
(The credit isn't all mine. Serde's issue 1583 basically has everything.)

Related

How to index struct values of different types

I'm new to Rust, probably missing something obvious. I have the following code with the main idea of being able to index any struct field like so struct_instance['field'].
use std::ops::Index;
enum Selection {
Full,
Partial,
}
struct Config {
display: bool,
timeout: u16,
selection: Selection,
}
impl Index<&'_ str> for Config {
type Output = bool;
fn index(&self, index: &'_ str) -> &Self::Output {
match index {
"display" => &self.display,
_ => panic!("Unknown field: {}", index),
}
}
}
fn main() {
let config = Config {
display: true,
timeout: 500,
selection: Selection::Partial,
};
let display = config["display"];
println!("{display}");
}
The problem is: I can not find a way to index every type of struct fields, because associated type Output doesn't let me define more than one type. I would want to have match being able to process all Config fields somehow, is there a way to do so?
As answered apilat , Index is for array like structures.
However if you want, you can achieve this with enums.
Create enum with all available types of config fields (bool, u16, Selection, etc...)
Change Config fields' types to this new enum
Change the Output in the Index impl again to this new enum
Here is full code example
use std::ops::Index;
#[derive(Debug)]
enum ConfigField {
Display(bool),
Timeout(u16),
Selection(Selection)
}
#[derive(Debug)]
enum Selection {
Full,
Partial,
}
struct Config {
display: ConfigField,
timeout: ConfigField,
selection: ConfigField,
}
impl Index<&'_ str> for Config {
type Output = ConfigField;
fn index(&self, index: &'_ str) -> &Self::Output {
match index {
"display" => &self.display,
"timeout" => &self.timeout,
"selection" => &self.selection,
_ => panic!("Unknown field: {}", index),
}
}
}
fn main() {
let config = Config {
display: ConfigField::Display(true),
timeout: ConfigField::Timeout(500),
selection: ConfigField::Selection(Selection::Partial),
};
let display = &config["display"];
let timeout = &config["timeout"];
let selection = &config["selection"];
println!("{:?} {:?} {:?}", display, timeout, selection);
}

How can I use rust Try trait with Option NoneError?

I've written a custom protocol where I've defined my own struct for a frame and it parses from bytes. My function accepts a Vec and parses the elements accordingly. To account for invalid frames, I am returning a Result<Frame> and calling .get() on the byte array. Here's my code:
fn main(){
let emptyvec = Vec::new();
match Frame::from_bytes(emptyvec) {
Err(e) => {
println!("Received invalid frame");
},
Ok(frame) => {
println!("Received valid frame");
}
}
}
struct Frame {
txflag: u8, // indicates if chunked
msgtype: u8, // a flag for message type
sender: u8, // which node ID sent this frame?
routeoffset: u8, // size of array of route for frame
route: Vec<u8>, // a list of node IDs that frame should pass
payload: Vec<u8>, // payload data
}
impl Frame {
/// parse from raw bytes
pub fn from_bytes(bytes: &Vec<u8>) -> std::io::Result<Self> {
let txflag = bytes.get(0)?.clone();
let msgtype = bytes.get(1)?.clone();
let sender = bytes.get(2)?.clone();
let routesoffset = bytes.get(3)?.clone();
let routes = &bytes.get(4..(4+routesoffset as usize))?;
let (left, right) = bytes.split_at(2);
let data = Vec::from(right);
Ok(Frame {
txflag,
msgtype,
sender,
routeoffset: routesoffset,
route: Vec::from(routes),
payload: data
})
}
}
However when I try to use this pattern I get the following compilation error, and when attempting to implement the trait I get an error that the Try trait is unstable.
error[E0277]: `?` couldn't convert the error to `std::io::Error`
--> src/stack/frame.rs:121:34
|
121 | let txflag = bytes.get(0)?.clone();
| ^ the trait `std::convert::From<std::option::NoneError>` is not implemented for `std::io::Error`
Not quite sure how to proceed but I'd like to use stable features to solve this. The goal here is to be able to parse bytes and handle an invalid frame as necessary.
This is probably what you want
use std::io::{Error, ErrorKind};
fn main() {
let emptyvec = Vec::new();
match Frame::from_bytes(&emptyvec) {
Err(e) => {
println!("Received invalid frame");
}
Ok(frame) => {
println!("Received valid frame");
}
}
}
struct Frame {
txflag: u8,
// indicates if chunked
msgtype: u8,
// a flag for message type
sender: u8,
// which node ID sent this frame?
routeoffset: u8,
// size of array of route for frame
route: Vec<u8>,
// a list of node IDs that frame should pass
payload: Vec<u8>, // payload data
}
impl Frame {
/// parse from raw bytes
pub fn from_bytes(bytes: &Vec<u8>) -> std::io::Result<Self> {
let txflag = bytes.get(0).ok_or(Error::from(ErrorKind::InvalidData))?.clone();
let msgtype = bytes.get(1).ok_or(Error::from(ErrorKind::InvalidData))?.clone();
let sender = bytes.get(2).ok_or(Error::from(ErrorKind::InvalidData))?.clone();
let routesoffset = bytes.get(3).ok_or(Error::from(ErrorKind::InvalidData))?.clone();
let routes = bytes
.get(4..(4 + routesoffset as usize))
.ok_or(Error::from(ErrorKind::InvalidData))?
.clone();
let (_, right) = bytes.split_at(2);
let data = Vec::from(right);
Ok(Frame {
txflag,
msgtype,
sender,
routeoffset: routesoffset,
route: Vec::from(routes),
payload: data,
})
}
}
Here is Rust Playground
You are trying to call ? on Option. You have to convert Option to Result (If you still want to use ?).
I want to add to what Đorðe Zeljić said:
As he already pointed out the result of bytes.get(0) is a std::option::Option. When you use the ? operator on that you already left the grounds of stable Rust. This application is only supported in unstable Rust at the moment.
If you want to stay in stable Rust, it's probably best to do what Đorðe wrote. If you want to keep using the ? operator because it produces nicer looking code, here is what's going on:
Rust has a lot of error types, each being only able to represent what they are made for. If you are using a std::io::Result this implicitly uses the error type std::io::Error which is only able to represent typical I/O errors. This type is not able to represent “there was no value when I expected one”. That's why from applying ? to a Option with the None value, you don't get a std::io::Error but a different kind of error: std::option::NoneError.
When your Rust application grows it will happen often, that you have to return a Result that can contain different types of errors. In that case you normally define your own error type (enum), that can represent different kinds of errors. Then for each error, that can be contained, you have to define the From trait on your own enum. This can be a lot of repeated work, so there is a macro in the quick-error crate, that helps with that and implements the From trait automatically for each error that can be contained.
To get your code compiling, you could define the following error enum, that can represent std::io::Error as well as std::option::NoneError:
quick_error! {
#[derive(Debug)]
pub enum FrameError {
IoError(err: std::io::Error) {from() cause(err)}
MissingValue(err: std::option::NoneError) {from()}
}
}
Instead of std::io::Result<Self> your from_bytes function then has to return a std::result::Result that uses your new error type: Result<Self, FrameError>.
Completely assembled that looks like this:
#![feature(try_trait)]
use quick_error::*;
quick_error! {
#[derive(Debug)]
pub enum FrameError {
IoError(err: std::io::Error) {from() cause(err)}
MissingValue(err: std::option::NoneError) {from()}
}
}
fn main() {
let emptyvec = Vec::new();
match Frame::from_bytes(&emptyvec) {
Err(_e) => {
println!("Received invalid frame");
}
Ok(_frame) => {
println!("Received valid frame");
}
}
}
struct Frame {
txflag: u8, // indicates if chunked
msgtype: u8, // a flag for message type
sender: u8, // which node ID sent this frame?
routeoffset: u8, // size of array of route for frame
route: Vec<u8>, // a list of node IDs that frame should pass
payload: Vec<u8>, // payload data
}
impl Frame {
/// parse from raw bytes
pub fn from_bytes(bytes: &Vec<u8>) -> Result<Self, FrameError> {
let txflag = bytes.get(0)?.clone();
let msgtype = bytes.get(1)?.clone();
let sender = bytes.get(2)?.clone();
let routesoffset = bytes.get(3)?.clone();
let routes = bytes.get(4..(4 + routesoffset as usize))?;
let (left, right) = bytes.split_at(2);
let data = Vec::from(right);
Ok(Frame {
txflag,
msgtype,
sender,
routeoffset: routesoffset,
route: Vec::from(routes),
payload: data,
})
}
}
To use the quick-error crate, you have to add the following to your Cargo.toml:
[dependencies]
quick-error = "1.2.3"

Why do I get an UnsupportedType error when serializing to TOML with a manually implemented Serialize for an enum with struct variants?

I'm trying to implement Serialize for an enum that includes struct variants. The serde.rs documentation indicates the following:
enum E {
// Use three-step process:
// 1. serialize_struct_variant
// 2. serialize_field
// 3. end
Color { r: u8, g: u8, b: u8 },
// Use three-step process:
// 1. serialize_tuple_variant
// 2. serialize_field
// 3. end
Point2D(f64, f64),
// Use serialize_newtype_variant.
Inches(u64),
// Use serialize_unit_variant.
Instance,
}
With that in mind, I proceeded to implemention:
use serde::ser::{Serialize, SerializeStructVariant, Serializer};
use serde_derive::Deserialize;
#[derive(Deserialize)]
enum Variants {
VariantA,
VariantB { k: u32, p: f64 },
}
impl Serialize for Variants {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
Variants::VariantA => serializer.serialize_unit_variant("Variants", 0, "VariantA"),
Variants::VariantB { ref k, ref p } => {
let mut state =
serializer.serialize_struct_variant("Variants", 1, "VariantB", 2)?;
state.serialize_field("k", k)?;
state.serialize_field("p", p)?;
state.end()
}
}
}
}
fn main() {
let x = Variants::VariantB { k: 5, p: 5.0 };
let toml_str = toml::to_string(&x).unwrap();
println!("{}", toml_str);
}
The code compiles, but when I run it it fails:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: UnsupportedType', src/libcore/result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
I figured the issue must be in my use of the API, so I consulted the API documentation for StructVariant and it looks practically the same as my code. I'm sure I'm missing something, but I don't see it based on the docs and output.
Enabling external tagging for the enum enables Serde to serialize/deserialize it to TOML:
#[derive(Deserialize)]
#[serde(tag = "type")]
enum Variants {
VariantA,
VariantB { k: u32, p: f64 },
}
toml::to_string(&Variants::VariantB { k: 42, p: 13.37 })
serializes to
type = VariantB
k = 42
p = 13.37
This works well in Vecs and HashMaps, too.
The TOML format does not support enums with values:
use serde::Serialize; // 1.0.99
use toml; // 0.5.3
#[derive(Serialize)]
enum A {
B(i32),
}
fn main() {
match toml::to_string(&A::B(42)) {
Ok(s) => println!("{}", s),
Err(e) => eprintln!("Error: {}", e),
}
}
Error: unsupported Rust type
It's unclear what you'd like your data structure to map to as TOML. Using JSON works just fine:
use serde::Serialize; // 1.0.99
use serde_json; // 1.0.40
#[derive(Serialize)]
enum Variants {
VariantA,
VariantB { k: u32, p: f64 },
}
fn main() {
match serde_json::to_string(&Variants::VariantB { k: 42, p: 42.42 }) {
Ok(s) => println!("{}", s),
Err(e) => eprintln!("Error: {}", e),
}
}
{"VariantB":{"k":42,"p":42.42}}

Is there a way to remove unwrapping of items in an iterator chain that filters and partitions based on a Result?

I created this example code:
fn main() {
let books = vec![
Book {
data: Ok("type1".to_owned()),
metadata: "meta1".to_owned(),
},
Book {
data: Err("-".to_owned()),
metadata: "meta2".to_owned(),
},
Book {
data: Ok("type2".to_owned()),
metadata: "meta2".to_owned(),
},
];
// metadata without data being error
let (book_type_1, book_type_2): &(Vec<_>, Vec<_>) = &books
.iter()
.filter(|f| f.data.is_ok())
.partition(|p| p.data.as_ref().unwrap() == "type1");
println!("Books {:?}", books);
println!("Type 1 {:?}", book_type_1); // Should be the original Vec<Book> with Type 1 filtered.
println!("Type 2 {:?}", book_type_2); // Should be the original Vec<Book> with Type 2 filtered.
}
#[derive(Debug)]
struct Book {
data: Result<String, String>,
metadata: String,
}
On the let (book_type_1, book_type_2) expression, I need to use Book::data twice, but I already filtered it so I know it can't be Err. Is there a way to restructure to remove the use of unwrap here?
I'm not sure exactly what you mean, but it seems like you want to use Iterator::filter_map(). It lets you filter for values that are Some(T), which then get passed on as unwrapped Ts.
So what you can do is convert your Results to Options with Result::ok(), so a Result::Ok(T) will become Some(T) which means it passes the filter as T.
fn main() {
let books = vec![
Book {
data: Ok("type1".to_owned()),
metadata: "meta1".to_owned(),
},
Book {
data: Err("-".to_owned()),
metadata: "meta2".to_owned(),
},
Book {
data: Ok("type2".to_owned()),
metadata: "meta2".to_owned(),
},
];
// metadata without data being error
let (book_type_1, book_type_2): (Vec<_>, Vec<_>) = books
.iter()
.filter_map(|f| {
match &f.data {
Ok(data) => Some((f, data)),
Err(_) => None,
}
})
.partition(|(book, data)| *data == "type1");
println!("Books {:?}", books);
println!("Type 1 {:?}", book_type_1);
println!("Type 2 {:?}", book_type_2);
}
#[derive(Debug)]
struct Book {
data: Result<String, String>,
metadata: String,
}
playground
I removed the unnecessary reference to the returned partitioned tuple.
Also note that None pertains to Option<T>, but you're using Result<T, E>. I think you knew that but just making sure.
I would use flat_map as described in the other answer, but I'd leave the yielded values as a Result. Result implements IntoIterator, so you can use it directly in flat_map.
I'd also use Result::as_ref instead of writing out the match explicitly.
I'd then use Itertools::partition_map to simultaneously select between the types and remove the extra property:
extern crate itertools;
use itertools::{Either, Itertools};
// ...
let (book_type_1, book_type_2): (Vec<_>, Vec<_>) = books
.iter()
.flat_map(|b| b.data.as_ref().map(|data| (b, data)))
.partition_map(|(b, data)| {
if data == "type1" {
Either::Left(b)
} else {
Either::Right(b)
}
});
Note:
There's no reason to take a reference to the result tuple.
This only works because you are iterating on references to books.
If you needed to operate on the owned Books, I'd move the comparison into the flat_map call and pass it along:
let (book_type_1, book_type_2): (Vec<_>, Vec<_>) = books
.into_iter()
.flat_map(|book| {
book.data
.as_ref()
.ok()
.map(|data| data == "type1")
.map(|is_type1| (is_type1, book))
})
.partition_map(|(is_type1, book)| {
if is_type1 {
Either::Left(book)
} else {
Either::Right(book)
}
});
There's also the pragmatic solution of sorting all errors into the same bin as one of the types. Since you know that there won't be any errors, this has no effect:
let (book_type_1, book_type_2): (Vec<_>, Vec<_>) = books
.iter()
.filter(|f| f.data.is_ok())
.partition(|p| p.data.as_ref().ok().map_or(false, |d| d == "type1"));
See also:
What's the most idiomatic way of working with an Iterator of Results?

How do I deserialize into trait, not a concrete type?

I'm trying to do struct serialization, in which the bytes would eventually be sent down a pipe, reconstructed and methods be called on them.
I created a trait these structs would implement as appropriate and I'm using serde and serde-cbor for serialization:
extern crate serde_cbor;
#[macro_use]
extern crate serde_derive;
extern crate serde;
use serde_cbor::ser::*;
use serde_cbor::de::*;
trait Contract {
fn do_something(&self);
}
#[derive(Debug, Serialize, Deserialize)]
struct Foo {
x: u32,
y: u32,
}
#[derive(Debug, Serialize, Deserialize)]
struct Bar {
data: Vec<Foo>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Baz {
data: Vec<Foo>,
tag: String,
}
impl Contract for Bar {
fn do_something(&self) {
println!("I'm a Bar and this is my data {:?}", self.data);
}
}
impl Contract for Baz {
fn do_something(&self) {
println!("I'm Baz {} and this is my data {:?}", self.tag, self.data);
}
}
fn main() {
let data = Bar { data: vec![Foo { x: 1, y: 2 }, Foo { x: 3, y: 4 }, Foo { x: 7, y: 8 }] };
data.do_something();
let value = to_vec(&data).unwrap();
let res: Result<Contract, _> = from_reader(&value[..]);
let res = res.unwrap();
println!("{:?}", res);
res.do_something();
}
When I try to reconstruct the bytes using the trait as the type (given that I wouldn't know which underlying object is being sent), the compiler complains that the trait does not implement the Sized trait:
error[E0277]: the trait bound `Contract: std::marker::Sized` is not satisfied
--> src/main.rs:52:15
|
52 | let res: Result<Contract, _> = from_reader(&value[..]);
| ^^^^^^^^^^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `Contract`
|
= note: `Contract` does not have a constant size known at compile-time
= note: required by `std::result::Result`
I guess it makes sense since the compiler doesn't know how big the struct is supposed to be and doesn't know how to line up the bytes for it. If I change the line where I deserialize the object to specify the actual struct type, it works:
let res: Result<Bar, _> = from_reader(&value[..]);
Is there a better pattern to achieve this serialization + polymorphism behavior?
It looks like you fell into the same trap that I fell into when I moved from C++ to Rust. Trying to use polymorphism to model a fixed set of variants of a type. Rust's enums (similar to Haskell's enums, and equivalent to Ada's variant record types) are different from classical enums in other languages, because the enum variants can have fields of their own.
I suggest you change your code to
#[derive(Debug, Serialize, Deserialize)]
enum Contract {
Bar { data: Vec<Foo> },
Baz { data: Vec<Foo>, tag: String },
}
#[derive(Debug, Serialize, Deserialize)]
struct Foo {
x: u32,
y: u32,
}
impl Contract {
fn do_something(&self) {
match *self {
Contract::Bar { ref data } => println!("I'm a Bar and this is my data {:?}", data),
Contract::Baz { ref data, ref tag } => {
println!("I'm Baz {} and this is my data {:?}", tag, data)
}
}
}
}
You can use typetag to solve the problem. Add #[typetag::serde] (or ::deserialize, as shown here) to the trait and each implementation:
use serde::Deserialize;
#[typetag::deserialize(tag = "driver")]
trait Contract {
fn do_something(&self);
}
#[derive(Debug, Deserialize, PartialEq)]
struct File {
path: String,
}
#[typetag::deserialize(name = "file")]
impl Contract for File {
fn do_something(&self) {
eprintln!("I'm a File {}", self.path);
}
}
#[derive(Debug, Deserialize, PartialEq)]
struct Http {
port: u16,
endpoint: String,
}
#[typetag::deserialize(name = "http")]
impl Contract for Http {
fn do_something(&self) {
eprintln!("I'm an Http {}:{}", self.endpoint, self.port);
}
}
fn main() {
let f = r#"
{
"driver": "file",
"path": "/var/log/foo"
}
"#;
let h = r#"
{
"driver": "http",
"port": 8080,
"endpoint": "/api/bar"
}
"#;
let f: Box<dyn Contract> = serde_json::from_str(f).unwrap();
f.do_something();
let h: Box<dyn Contract> = serde_json::from_str(h).unwrap();
h.do_something();
}
[dependencies]
serde_json = "1.0.57"
serde = { version = "1.0.114", features = ["derive"] }
typetag = "0.1.5"
See also:
How can deserialization of polymorphic trait objects be added in Rust if at all?
Adding on to oli_obk's answer, you can use Serde's enum representation to distinguish between the types.
Here, I use the internally-tagged representation to deserialize these two similar objects into the appropriate variant:
{
"driver": "file",
"path": "/var/log/foo"
}
{
"driver": "http",
"port": 8080,
"endpoint": "/api/bar"
}
use serde; // 1.0.82
use serde_derive::*; // 1.0.82
use serde_json; // 1.0.33
#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "driver")]
enum Driver {
#[serde(rename = "file")]
File { path: String },
#[serde(rename = "http")]
Http { port: u16, endpoint: String }
}
fn main() {
let f = r#"
{
"driver": "file",
"path": "/var/log/foo"
}
"#;
let h = r#"
{
"driver": "http",
"port": 8080,
"endpoint": "/api/bar"
}
"#;
let f: Driver = serde_json::from_str(f).unwrap();
assert_eq!(f, Driver::File { path: "/var/log/foo".into() });
let h: Driver = serde_json::from_str(h).unwrap();
assert_eq!(h, Driver::Http { port: 8080, endpoint: "/api/bar".into() });
}
You don't have to squash it all into one enum, you can create separate types as well:
#[derive(Debug, Deserialize, PartialEq)]
#[serde(tag = "driver")]
enum Driver {
#[serde(rename = "file")]
File(File),
#[serde(rename = "http")]
Http(Http),
}
#[derive(Debug, Deserialize, PartialEq)]
struct File {
path: String,
}
#[derive(Debug, Deserialize, PartialEq)]
struct Http {
port: u16,
endpoint: String,
}