How to implement FromBencode for enum
morr0ne opened this issue · 3 comments
I'm trying to implement FromBencode for an enum but I'm at a loose, decode_bencode_object doesn't take &self as a parameter so I don't have anything to match against to choose an enum variant
Relevant code
pub enum Info {
SingleFile {
length: u64,
md5sum: Option<String>,
name: String,
piece_length: u64,
pieces: Vec<u8>,
private: Option<u8>,
},
MultiFile {
files: Vec<File>,
name: String,
pieces: Vec<u8>,
piece_length: u64,
private: Option<u8>,
},
}
impl FromBencode for MetaInfo {
const EXPECTED_RECURSION_DEPTH: usize = 3;
fn decode_bencode_object(object: Object) -> Result<Self, DecodingError>
where
Self: Sized,
{
// I can't match self because the trait signature doesn't include it
}
}
I was able to easily implement ToBencode by matching self
Relevant code
impl ToBencode for Info {
const MAX_DEPTH: usize = 1;
fn encode(&self, encoder: SingleItemEncoder) -> Result<(), EncodingError> {
match self {
Info::SingleFile {
name,
length,
md5sum,
piece_length,
pieces,
private,
} => encoder.emit_unsorted_dict(|e| {
e.emit_pair(b"name", name)?;
e.emit_pair(b"length", length)?;
if let Some(sum) = md5sum {
e.emit_pair(b"md5sum", sum)?;
}
e.emit_pair(b"piece length", piece_length)?;
e.emit_pair(b"pieces", AsString(pieces))?;
if let Some(p) = private {
e.emit_pair(b"private", p)?;
}
Ok(())
})?,
Info::MultiFile {
name,
files,
piece_length,
pieces,
private,
} => encoder.emit_unsorted_dict(|e| {
e.emit_pair(b"name", name)?;
e.emit_pair(b"files", files)?;
e.emit_pair(b"piece length", piece_length)?;
e.emit_pair(b"pieces", AsString(pieces))?;
if let Some(p) = private {
e.emit_pair(b"private", p)?;
}
Ok(())
})?,
}
Ok(())
}
}
Hopefully, I'm missing something obvious but I haven't found any example of this
FromBencode doesn't take an &self
because when decode_bencode_object
is called, we don't have an instance of Self
.
For your usecase, I would recommend iterating over the fields in the encoded dictionary and store the decoded objects in local variables. At the end, when you construct the final result object, you can tell whether it was a single file torrent or a multifile torrent based on whether there was a files
field:
fn decode_bencode_object(object: Object) -> Result<Self, DecodingError> {
let mut decoder = object.try_into_dictionary()?;
let mut name = None;
let mut files = None;
let mut pieces = None;
// ...
while let Some((key, value)) = decoder.next_pair()? {
match key {
b"name" => name = Some(FromBencode::decode_bencode_object(value)?),
b"files" => files = Some(FromBencode::decode_bencode_object(value)?),
b"pieces" => pieces = Some(FromBencode::decode_bencode_object(value)?),
// add the rest of the fields here
_ => (), // Ignore unrecognized fields. Alternatively, you might throw an error here
}
}
// Here's the tricky bit: we know that this object is a MultiFile if files is not None
if files.is_some() {
Ok(Info::MultiFile{
name: name.ok_or_else(|| DecodingError::missing_field("name"))?,
// the rest of the fields...
});
} else {
Ok(Info::SingleFile{
// add all the applicable fields here
})
}
}
(Of course, this code is just a sketch which I wrote in the comment field here; I provide no guarantees that it compiles or runs as is, but it's based on a pattern that we have a lot of instances of in production)
I don't understand too much about bendy internals so I didn't understand why I didn't have an instance of self
I haven't tried yet but this seems like the optimal (if not the only) solution.
Considering how common implementing FromBencode for an enum is, perhaps providing an example in the repo would be a good idea?
Closing this as it's resolved.