alloy_sol_macro_input/
input.rs
1use ast::Spanned;
2use std::path::PathBuf;
3use syn::{
4 parse::{discouraged::Speculative, Parse, ParseStream},
5 Attribute, Error, Ident, LitStr, Result, Token,
6};
7
8#[derive(Clone, Debug)]
11pub enum SolInputKind {
12 Type(ast::Type),
14 Sol(ast::File),
16 #[cfg(feature = "json")]
18 Json(Ident, alloy_json_abi::ContractObject),
19}
20
21impl Parse for SolInputKind {
22 fn parse(input: ParseStream<'_>) -> Result<Self> {
23 let fork = input.fork();
24 match fork.parse() {
25 Ok(file) => {
26 input.advance_to(&fork);
27 Ok(Self::Sol(file))
28 }
29 Err(e) => match input.parse() {
30 Ok(ast::Type::Custom(_)) | Err(_) => Err(e),
31
32 Ok(ast::Type::Mapping(m)) => {
33 Err(Error::new(m.span(), "mapping types are not yet supported"))
34 }
35
36 Ok(ty) => Ok(Self::Type(ty)),
37 },
38 }
39 }
40}
41
42#[derive(Clone, Debug)]
46pub struct SolInput {
47 pub attrs: Vec<Attribute>,
49 pub path: Option<PathBuf>,
51 pub kind: SolInputKind,
53}
54
55impl Parse for SolInput {
56 fn parse(input: ParseStream<'_>) -> Result<Self> {
57 let attrs = Attribute::parse_inner(input)?;
58
59 let fork = input.fork();
61 let _fork_outer = Attribute::parse_outer(&fork)?;
62
63 let is_litstr_like = |fork: syn::parse::ParseStream<'_>| {
65 fork.peek(LitStr) || (fork.peek(Ident) && fork.peek2(Token![!]))
66 };
67
68 if is_litstr_like(&fork)
69 || (fork.peek(Ident) && fork.peek2(Token![,]) && {
70 let _ = fork.parse::<Ident>();
71 let _ = fork.parse::<Token![,]>();
72 is_litstr_like(&fork)
73 })
74 {
75 Self::parse_abigen(attrs, input)
76 } else {
77 input.parse().map(|kind| Self { attrs, path: None, kind })
78 }
79 }
80}
81
82impl SolInput {
83 fn parse_abigen(mut attrs: Vec<Attribute>, input: ParseStream<'_>) -> Result<Self> {
85 attrs.extend(Attribute::parse_outer(input)?);
86
87 let name = input.parse::<Option<Ident>>()?;
88 if name.is_some() {
89 input.parse::<Token![,]>()?;
90 }
91 let span = input.span();
92 let macro_string::MacroString(mut value) = input.parse::<macro_string::MacroString>()?;
93
94 let _ = input.parse::<Option<Token![,]>>()?;
95 if !input.is_empty() {
96 let msg = "unexpected token, expected end of input";
97 return Err(Error::new(input.span(), msg));
98 }
99
100 let mut path = None;
101
102 let is_path = {
103 let s = value.trim();
104 !(s.is_empty()
105 || (s.starts_with('{') && s.ends_with('}'))
106 || (s.starts_with('[') && s.ends_with(']')))
107 };
108 if is_path {
109 let mut p = PathBuf::from(value);
110 if p.is_relative() {
111 let dir = std::env::var_os("CARGO_MANIFEST_DIR")
112 .map(PathBuf::from)
113 .ok_or_else(|| Error::new(span, "failed to get manifest dir"))?;
114 p = dir.join(p);
115 }
116 p = dunce::canonicalize(&p)
117 .map_err(|e| Error::new(span, format!("failed to canonicalize path {p:?}: {e}")))?;
118 value = std::fs::read_to_string(&p)
119 .map_err(|e| Error::new(span, format!("failed to read file {p:?}: {e}")))?;
120 path = Some(p);
121 }
122
123 let s = value.trim();
124 if s.is_empty() {
125 let msg = if is_path { "file path is empty" } else { "empty input is not allowed" };
126 Err(Error::new(span, msg))
127 } else if (s.starts_with('{') && s.ends_with('}'))
128 || (s.starts_with('[') && s.ends_with(']'))
129 {
130 #[cfg(feature = "json")]
131 {
132 let json = serde_json::from_str(s)
133 .map_err(|e| Error::new(span, format!("invalid JSON: {e}")))?;
134 let name = name.ok_or_else(|| Error::new(span, "need a name for JSON ABI"))?;
135 Ok(Self { attrs, path, kind: SolInputKind::Json(name, json) })
136 }
137 #[cfg(not(feature = "json"))]
138 {
139 let msg = "JSON support must be enabled with the \"json\" feature";
140 Err(Error::new(span, msg))
141 }
142 } else {
143 if let Some(name) = name {
144 let msg = "names are not allowed outside of JSON ABI, remove this name";
145 return Err(Error::new(name.span(), msg));
146 }
147 let kind = syn::parse_str(s).map_err(|e| {
148 let msg = format!("expected a valid JSON ABI string or Solidity string: {e}");
149 Error::new(span, msg)
150 })?;
151 Ok(Self { attrs, path, kind })
152 }
153 }
154}