ion_proc/attribute/
name.rs1use convert_case::{Case, Casing as _};
8use proc_macro2::{Ident, Span, TokenStream};
9use quote::format_ident;
10use syn::parse::{Parse, ParseStream};
11use syn::{Error, Expr, ExprLit, ExprPath, Lit, LitStr, parse_quote};
12
13#[derive(Clone)]
14pub(crate) enum Name {
15 String(LitStr),
16 Symbol(ExprPath),
17}
18
19impl Name {
20 pub(crate) fn from_string<S: AsRef<str>>(string: S, span: Span) -> Name {
21 Name::String(LitStr::new(string.as_ref(), span))
22 }
23
24 pub(crate) fn as_string(&self) -> String {
25 match self {
26 Name::String(literal) => literal.value(),
27 Name::Symbol(path) => path.path.segments.last().map(|segment| format!("[{}]", segment.ident)).unwrap(),
28 }
29 }
30
31 pub(crate) fn to_property_spec(&self, ion: &TokenStream, function: &mut Ident) -> (Box<Expr>, Box<Expr>) {
32 match self {
33 Name::String(literal) => {
34 let mut name = literal.value();
35 if name.is_case(Case::UpperSnake) {
36 name = name.to_case(Case::Camel);
37 }
38 (
39 Box::new(Expr::Lit(ExprLit {
40 attrs: Vec::new(),
41 lit: Lit::Str(LitStr::new(&name, literal.span())),
42 })),
43 parse_quote!(#ion::flags::PropertyFlags::CONSTANT_ENUMERATED),
44 )
45 }
46 Name::Symbol(symbol) => {
47 *function = format_ident!("{}_symbol", function);
48 (
49 Box::new(Expr::Path(symbol.clone())),
50 parse_quote!(#ion::flags::PropertyFlags::CONSTANT),
51 )
52 }
53 }
54 }
55}
56
57impl Parse for Name {
58 fn parse(input: ParseStream) -> syn::Result<Name> {
59 if let Ok(literal) = input.parse::<LitStr>() {
60 let string = literal.value();
61 if !string.starts_with('[') && !string.ends_with(']') {
62 Ok(Name::String(literal))
63 } else {
64 Err(Error::new(
65 literal.span(),
66 "Function name must not start with '[' or end with ']'",
67 ))
68 }
69 } else if let Ok(other) = input.parse() {
70 Ok(Name::Symbol(other))
71 } else {
72 Err(Error::new(input.span(), "Function name is not a string or expression"))
73 }
74 }
75}