Help with Serde Json Value and Creating/Writing in a File(Ruby to Rust)

Just some context for the code:

// Open aws_instances
    file = File::open("aws_instances").expect("File can't be opened.");
    // Create aws_tags file
    let mut tag_file = File::create("aws_tags.sh");

    // Read aws_instances
    let mut instances = String::new(); 
    file.read_to_string(&mut instances).expect("File can't be read");

    #[derive(serde::Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct InstancesList {
        reservations: Vec<Reservation>,
    }

    #[derive(serde::Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Reservation {
        instances: Vec<Instance>,
    }

    #[derive(serde::Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Instance {
        instance_lifecycle: String,
        placement: Placement,
        r#type: String,
        tags: Vec<InstanceTag>,
        instance_id: String,
    }

    #[derive(serde::Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct InstanceTag {
        key: String,
        value: String,
    }

    #[derive(serde::Deserialize)]
    #[serde(rename_all = "PascalCase")]
    struct Placement {
        availability_zone: String,
    }   

    // Parse aws_instances as JSON
    let instances_list: InstancesList = 
        serde_json::from_str(&instances).expect("Json was not well formatted.");
    
    // Create a new HashMap
    let mut ec2 = HashMap::new();
    for reservation in &instances_list.reservations{
        for instance in &reservation.instances{
              ...etc
        }
    }

I was wondering how to check if instance contains a key. In ruby it would be:

if instance.has_key?("Tags")

For rust I tried adding to the serde json struct, but it says "method not found in &main::Instance " :

instance: HashMap<String, String> //so then I could possibly write: 
if instance.contains_key("Tags"){

Additionally, I have an error "expected struct Vec, found tuple"

let msg = ("{}", name);
ec2.insert(key, "{}", name);
let msg = ("{}:*{}*", name, eol);
ec2.insert(key, msg);

The ruby line for the corresponding code is:

ec2[key] << "#{name}"
ec2[key] << "#{name}:*#{eol}*"

Finally, I am trying to create a file and write in it:

let mut tag_file = File::create("aws_tags.sh");
//the following line is in the for loop listed in the context above: 
let data = ("aws ec2 create-tags --region us-west-2 --resources {}  --tags Key=opsworks:layer,Value='#{layer_name}'\n", &instance.instance_id);
                        aws_tags.write_all(&data);

I get the error that it isn't in the scope, which I understand since I'm trying to write it inside the for loops. Is there a way I can do this?
Thanks for your time and help.

In your Rust code, tags is a field of the Instance struct, so the struct always has this field. You can check whether the field contains any data like this:

if !instance.tags.is_empty() { ... }

You can use the format! macro to do string interpolation:

let msg = format!("{}:*{}*", name, eol);

Your File variable is named tag_file, not aws_tags. This should be:

tag_file.write_all(&data);
1 Like

Thanks for the help so far. For some reason I get the error "method not found in Result<File, std::io::Error>" when I try to write

tag_file.write_all(&data);

Also I get the following error: "expected struct Vec, found struct std::string::String", but I'm not sure why it thinks that it should be a vector. Should I explicitly state HashMap's types?

ec2.insert(key, msg);

One last thing, how can I close tag_file. I tried tag_file.close() but I get the same error for tag_file.write().
Thanks.

File::create may return an error, so you need to handle the error before you can access the File. For example, you can use unwrap which will panic on error:

let mut tag_file = File::create("aws_tags.sh").unwrap();

Yes, adding a type annotation would be useful:

let mut ec2: HashMap<String, String> = HashMap::new();

Now you'll probably get a new error showing that you were inserting a vector instead of a string somewhere, which made the compiler infer the wrong type for the map.

The file is closed automatically when the tag_file variable goes out of scope.

1 Like

Thanks for the prompt response, but I still have a couple more errors.

let data = format!("aws ec2 create-tags --region us-west-2 --resources {}  --tags Key=opsworks:layer,Value='{}'\n", &instance.instance_id, layer_name);
tag_file.write_all(&data);

I keep getting an error that it expected a "expected slice [u8]". How can I make it so that it accepts a string?
Lastly, I realized why I was getting that mismatch in type. I was adding a String vector to ec2. How can I add values to that vector? I tried:

let msg = format!("{}:*{}*", name, eol);
ec2.get(key).push(msg); 

ec2.get(key) return the vector value stored, but I get the error: method not found in std::option::Option<&Vec<std::string::String>>.

You can use the as_bytes method to convert a string to a [u8] slice:

tag_file.write_all(data.as_bytes());

or you can use the write! macro to do formatting and writing at the same time:

write!(&mut tag_file, "aws ec2 create-tags --region us-west-2 --resources {}  --tags Key=opsworks:layer,Value='{}'\n", &instance.instance_id, layer_name);

HashMap::entry can be useful for dealing with a map of vectors. For example:

// If `key` is not already in `ec2`, this will insert an empty vector, and then it will push `msg`:
ec2.entry(key).or_default().push(msg);
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.