Another variation of enums and struct problems

I have four different types that represent NETCONF with quick_xml that reside in a module as .rs modules. Staying the NETCONF config they look like this.

nokia_ixr_x_config::RpcReply
nokia_ixr_s_config::RpcReply

In a separate netconf module, that does get and get_config I represent those with an enum

pub enum WhichRpcReply {
    RpcError{ error: rpc_error::RpcReply },
    XConfigReply{ config: nokia_ixr_x_config::RpcReply},
    SConfigReply{ config: nokia_ixr_s_config::RpcReply},
}

Below, I represent the configurations with another enum.

#[derive(Debug, Clone)]
pub enum Config {
    //Error(WhichRpcReply ),
    XConfig( WhichRpcReply),
    SConfig( WhichRpcReply ),
}

Here is an example of how the get_config creates instances.

if self.equipment_type.as_str() == "ixr-s" {
   match de::from_str::<nokia_ixr_s_config::RpcReply>(&filtered_config) {
       Ok(res) => return Some(Config::SConfig(WhichRpcReply::SConfigReply{config: res})),
       Err(err) => {
           let msg = err.to_string();
           error!(msg, "Problem deserializing an ixr_s config: {:?}", msg);
       }
   }
}

And in my tests...

let result = nc.get_config("running", None);
if result.is_some() {
     let c = result.unwrap();
     let l = c.rpc_reply();
            if l.is_some() {
                let m = l.unwrap();
                println!("{:?}", m);
            }
        };

In the debugger, it looks like I expect.

But I do not get anything I can access with a "." operator.

It's hard to say anything given the information you have provided, but you are probably not deserializing the config to a struct (i.e. to a hashmap instead).

Jeez... that was a sloppy post. Just edited it.

It looks like I lose the deserialized object when I put into an Enum or Struct. Maybe I have a type problem?

It looks like you're not quite clear on the basics of enum's and match'es on them. Take a look here. In your case, your m is WhichRpcReply - meaning, you can do any of the following:

use WhichRpcReply::*;
match m {
    RpcError { error } => todo!("handle error"),
    XConfigReply { config } => todo!("handle x-config"),
    SConfigReply { config } => todo!("handle s-config"),
}

or

if let WhichRpcReply::SConfigReply { config } = m {
    todo!("handle s-config");
}

or

let WhichRpcReply::SConfigReply { config } = m
    else { return Err("unexpected config") };

Compare all the if and .unwrap() against these:

Summary
let Some(config) = nc.get_config("running", None) else { return; } // match or skip
let Some(which) = config.rpc_reply() else { return; } // same thing here
match which { ... } // see the above
2 Likes

This is turning into a bit of a mess.

You have replies which contain configs which contain replies...

I mean

Perhaps the world of NETCONF is just simply horrendous, but I think we'll need a bit of a rundown of what entities exist and what their relationships are.

Can a Config::XConfig(_) contain a WhichRPCReply::SConfigRepl, i.e. can an XConfig get a SConfig reply?

I do not get anything I can access with a "." operator.
What do you mean here? Are you referring to code completions in your IDE?

I think you mean that m, which is an enum doesn't allow you to access the config field that belongs to some of its variants.

You just can't do that with rust, if you want to access the config field of some variants you have to match:

match m {
    WhichRpcReply::SConfigReply { config } => {
        // Now config is in scope and is a nokia_ixr_s_config::RpcReply
    }
}

That's just how enums work.

Gentle reminder that structs also exist :slight_smile: and do allow you to access their (public) fields directly since they have a fixed known type (layout, fields) at compile time.

2 Likes

Have you gone through the book yet? Rust is not a language (for most people anyway) that can be learned by trial and error, a bit at a time. Once you know the basics, however, you'll have the tools for learning more.

I got it. Here's how to get the actual RpcReply back out.

if result.is_some() {
       let c = result.unwrap();
       let d = match c {
              WhichRpcReply::SConfigReply{config: c} => c,
              WhichRpcReply::XConfigReply{config: c} => c,
              WhichRpcReply::RpcError{error: c} => c,
      };

I originally started with an enum of Config structs, then Config enums. My biggest challenge was getting all different types of RpcReplies based on the modules, nokia_ixr_s_config::RpcReply, nokia_ixr_x_config::RpcReply, and rpc_error::RpcReply into one type. I couldn't do that with structs and keep them all straight.

I do need to move that match up into an impl of WhichRpcReply.

This is not the way Option values are intended to be used, because it requires an unwrap for no good reason. Instead, use if let:

if let Some(c) = result {

It is important to become familiar with pattern matching and the different ways it can be used.

1 Like

Holy nested Enum of Structs of Enums...

 let c = result.unwrap();
match c {
        Config::SConfig(SConfigRpcReply { config: WhichRpcReply::SConfigReply(c)}) => {
           let conf = ConfigItems { name: c.data.configure.system.name, 
                      location: c.data.configure.system.location };
        }
       Config::XConfig(XConfigRpcReply { config: WhichRpcReply::XConfigReply(c)}) => {
           let conf = ConfigItems { name: c.data.configure.system.name, 
                      location: c.data.configure.system.location };
}
     Config::Error(RpcError {error: WhichRpcReply::RpcError(c)}) => println!("{:?}", c.rpc_error),
     _ => todo!(),
 };

It would be so nice to get one of those "conf"s out of that match so I don't have to write duplicate code.

1 Like

Are you aware you can do let c = match ...? Each arm of the match can return a value that will be bound to c. The match is an expression.

1 Like

Yes, I am aware. I have to work out the rpc_error arm of the match to do that.

It looks like your outer enum is not providing any value.

Back to Chapters 5 & 6 of the book!