How to wrap multiple types of readers into one?

I'm trying to implement a support for OSM XML files, and they may come in several compressed formats, or just plain uncompressed XML:

So I must declare parser type from xml-rs, right away with a nested type:

let parser: EventReader<Decoder<File>>;
match file_extension {
    ".osm" => { parser = EventReader::new(File::new(...)) },
    ".osm.gz" => { parser = EventReader::new(ZlibDecoder(...)) },
    ...

But this won't compile, and I can't put let in match clauses, because of scoping.

I thought I'd make a wrapper enum type, but it seems that I need to implement Read and BufRead traits, which is quite a lot of code.

What are other options?

I'm thinking of declaring a function with a generic type. Will this work?

fn parse_xml(rd: EventReader<R>)
    where R: Read {
    // do parse
}

let file = File::open(input_path)?;
match my_file_type {
    ".osm" => { parse_xml(EventReader::new(BufReader(file)) },
    ".osm.gz" => { parse_xml(EventReader::new(ZlibDecoder(file)) },
   ".osm.bz2" => { parse_xml(EventReader::new(Decompress(file)) }
   _ => { return Err("unsupported format".into()) }
}

You might also be able to use a trait object (unless you're using one of the methods on those traits that requires Sized). But your generic function seems like a simpler solution in this case at least.

2 Likes

I'd normally use a Box<dyn Read> trait object under these circumstances.

let reader = match file_extension {
  ".osm" => Box::new(File::open(...)?) as Box<dyn Read>,
  ".osm.gz" => Box::new(ZLibDecoder::new(...)) as Box<dyn Read>,
};

let events = EventReader::new(BufReader::new(reader));
...

That way you can separate the task of opening your file and getting a reader from the code actually parsing XML.

4 Likes