P3KI/bendy

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.