1

I wanted to be able to retrieve the content from an attribute like this:

#[foreign_key(table = "some_table", column = "some_column")]

This is how I am trying:

impl TryFrom<&&Attribute> for EntityFieldAnnotation {
    type Error = syn::Error;

    fn try_from(attribute: &&Attribute) -> Result<Self, Self::Error> {
        if attribute.path.is_ident("foreign_key") {
            match attribute.parse_args()? {
                syn::Meta::NameValue(nv) => 
                    println!("NAME VALUE: {:?}, {:?}, {:?}", 
                        nv.path.get_ident(), 
                        nv.eq_token.to_token_stream(),
                        nv.lit.to_token_stream(),
                    ),
                _ => println!("Not interesting")
            }
        } else {
            println!("No foreign key")
        }

    // ... More Rust code
}

Everything works fine if I just put in there only one NameValue. When I add the comma, everything brokes.

The only error: error: unexpected token

How can I fix my logic to enable the possibility of have more than just one NameValue?

Thanks

Alex Vergara
  • 1,766
  • 1
  • 10
  • 29

1 Answers1

4

UPDATE: While writing this answer, I had forgotten that Meta has List variant as well which gives you NestedMeta. I would generally prefer doing that instead of what I did in the answer below for more flexibility.

Although, for your particular case, using Punctuated still seems simpler and cleaner to me.


MetaNameValue represents only a single name-value pair. In your case it is delimited by ,, so, you need to parse all of those delimited values as MetaNameValue instead.

Instead of calling parse_args, you can use parse_args_with along with Punctuated::parse_terminated:

use syn::{punctuated::Punctuated, MetaNameValue, Token};

let name_values: Punctuated<MetaNameValue, Token![,]> = attribute.parse_args_with(Punctuated::parse_terminated).unwrap(); // handle error instead of unwrap

Above name_values has type Punctuated which is an iterator. You can iterate over it to get various MetaNameValue in your attribute.


Updates based on comments:

Getting value out as String from MetaNameValue:

let name_values: Result<Punctuated<MetaNameValue, Token![,]>, _> = attr.parse_args_with(Punctuated::parse_terminated);

match name_values {
    Ok(name_value) => {
        for nv in name_value {
            println!("Meta NV: {:?}", nv.path.get_ident());
            let value = match nv.lit {
                syn::Lit::Str(v) => v.value(),
                _ => panic!("expeced a string value"), // handle this err and don't panic
            };
            println!( "Meta VALUE: {:?}", value )
        }
    },
    Err(_) => todo!(),
};
Mihir Luthra
  • 6,059
  • 3
  • 14
  • 39
  • Works fine, but I am struggling getting the value in the `MetaNameValue`. Can you help? – Alex Vergara Apr 10 '22 at 18:21
  • Mhmm, `meta_name_value.lit` should give you the value. Can you be more specific about what are you struggling with? – Mihir Luthra Apr 10 '22 at 18:25
  • Sorry, I am struggling on how to get an `&str` from the `meta_name_value.lit` – Alex Vergara Apr 10 '22 at 18:33
  • Try to follow docs on [MetaNameValue](https://docs.rs/syn/latest/syn/struct.MetaNameValue.html). Type of `lit` is `Lit`. Go to doc of `Lit` and you will see it is an enum which has a variant [`Lit::Str`](https://docs.rs/syn/latest/syn/enum.Lit.html#variant.Str). Match for that variant and you will get [`LitStr`](https://docs.rs/syn/latest/syn/struct.LitStr.html). `LitStr` has a method called [`LitStr::value`](https://docs.rs/syn/latest/syn/struct.LitStr.html#method.value) which will give you a `String`. – Mihir Luthra Apr 10 '22 at 18:37
  • That's the exact part of where I am struggling. I am matching the LitStr(v) and getting `expected tuple struct or tuple variant, found function `syn::Lit` not a tuple struct or tuple variant` I am not understanding something, or maybe I should stop until tomorrow (rofl). – Alex Vergara Apr 10 '22 at 18:42
  • This is what I am trying: ``` match name_values { Ok(name_value) => { for nv in name_value { println!("Meta NV: {:?}", nv.path.get_ident()); let value: &str = match nv.lit { syn::LitStr(v) => v.value() }; println!( "Meta VALUE: {:?}", value ) } }, Err(_) => todo!(), } ``` – Alex Vergara Apr 10 '22 at 18:43
  • I have updated my answer to fix the snippet. Seems like you are tired and making silly mistakes, get a good night sleep :). – Mihir Luthra Apr 10 '22 at 18:53
  • This `LitStr` instead of `Lit::Str`. The tiredness is real. Thanks for your kind words an response :) – Alex Vergara Apr 10 '22 at 19:00