Yaml parser iteration

Hi,

I am totally new to Rust, this is the first time I am actually trying to achieve something with it even though I 've read some bit there and there. I know at some time I will have to dive into the book but I would like to solve this simple problem first.

My use case is simple: I would like to parse a YAML file and update a specific field value.

I am using the yaml_rust crate for that purpose. So far I was able to parse the content, turn it into a LinkedHashMap<yaml_rust::Yaml>.

Now I am wondering what would be an efficient way to iterate over this collection to update the field value I am looking for (this is a 3rd level deep nested field) ? I am mainly wondering two things:

  1. Am I using this crate the way it's supposed to be ?
  2. How can I iterate over nested fields ?

This is where I am so far (sorry if that looks quite ugly):

extern crate yaml_rust;
use yaml_rust::{YamlLoader};

use std::fs::File;
use std::io::prelude::*;

fn main() {
    let mut f = File::open("./template.yaml").expect("Error while opening file");
    let mut contents = String::new();
    f.read_to_string(&mut contents).expect("Something went wrong while reading the file");

    let docs =  YamlLoader::load_from_str(&mut contents).unwrap();
    let doc = docs[0].as_hash().unwrap();
    let iter = doc.iter();

    for item in iter {
        println!("{:?}", &item);
    }
}

Regards,
O

(Small note: std::fs::read_to_string is a nice replacement for you first three lines.)

To iterate over nested fields, since the map can contain any type of values, you need to match or use the as_* methods on every level. It seems like there are no mutable versions of the latter, so if you need mutable iterators you have to use matching:

match item {
    Yaml::Hash(ref mut map) => { // iterate and/or modify the map
    }
}

Thank you for your reply, indeed std::fs::read_to_string was a nice replacement :slight_smile:

I am trying to use the match you provided, is it supposed to be a replacement for the for in loop or should it be placed inside ? As of now if i put it inside, the compiler throw an error about expecting a tuple when it found an enum (yaml_rust::Yaml).

I was only generally outlining how to get at the sub-maps contained in Yaml values. In the for loop, the individual items are probably (key, value) pairs.

Ah yeah sorry my bad, I got something that kind of works now:

for item in iter {
    // println!("{:?}", &item.0);
    match item.1 {
        Yaml::Hash(ref map) => { // iterate and/or modify the map
          println!("{:?}", map);
        },
        _ => ()
    }
}

However I am facing a recursive situation there and I am not sure what's the elegant way to deal with it in Rust. As the map's value is another hash in which I must iterate over and search until I find the Key name I am searching for.

Thanks a lot for all the help provided so far

Can you provide a concrete example of the yaml structure and what key you'd like to change to what value?

Sure that's basically any AWS SAM templates.

For exemple: https://github.com/awslabs/serverless-application-model/blob/master/examples/apps/microservice-http-endpoint/template.yaml

And I would like to update the CodeUri's value for instance

So here's one way to implement the walk:

let mut docs = YamlLoader::load_from_str(...).unwrap();
visit_yaml(&mut docs[0], &Yaml::String("CodeUri".into()));

fn visit_yaml(yaml: &mut Yaml, looking_for: &Yaml) -> bool {
    if let Yaml::Hash(hash) = yaml {
        visit_hash(hash, looking_for)
    } else {
        false
    }
}

fn visit_hash(hash: &mut Hash, looking_for: &Yaml) -> bool {
    if let Some(val) = hash.get_mut(looking_for) {
        *val = Yaml::String("0ver".into());
        return true;
    };
    for (_, yaml) in hash {
        if visit_yaml(yaml, looking_for) {
            return true;
        }
    }
    false
}

But since it sounds like you know the exact schema/structure a priori, I think you can just drill down by finding the next level's Yaml::Hash via the same get_mut() approach as the above walk.