ethcontract_generate/
util.rs

1use anyhow::{anyhow, Result};
2#[cfg(feature = "http")]
3use curl::easy::Easy;
4use ethcontract_common::Address;
5use inflector::Inflector;
6use proc_macro2::{Ident, Literal, Span, TokenStream};
7use quote::quote;
8use syn::Ident as SynIdent;
9
10/// Expands a identifier string into an token.
11pub fn ident(name: &str) -> Ident {
12    Ident::new(name, Span::call_site())
13}
14
15/// Expands an identifier string into a token and appending `_` if the
16/// identifier is for a reserved keyword.
17///
18/// Parsing keywords like `self` can fail, in this case we add an underscore.
19pub fn safe_ident(name: &str) -> Ident {
20    syn::parse_str::<SynIdent>(name).unwrap_or_else(|_| ident(&format!("{}_", name)))
21}
22
23/// Expands a positional identifier string that may be empty.
24///
25/// Note that this expands the parameter name with `safe_ident`, meaning that
26/// identifiers that are reserved keywords get `_` appended to them.
27pub fn expand_input_name(index: usize, name: &str) -> TokenStream {
28    let name_str = match name {
29        "" => format!("p{}", index),
30        n => n.to_snake_case(),
31    };
32    let name = safe_ident(&name_str);
33
34    quote! { #name }
35}
36
37/// Expands a doc string into an attribute token stream.
38pub fn expand_doc(s: &str) -> TokenStream {
39    let doc = Literal::string(s);
40    quote! {
41        #[doc = #doc]
42    }
43}
44
45/// Parses the given address string
46pub fn parse_address<S>(address_str: S) -> Result<Address>
47where
48    S: AsRef<str>,
49{
50    let address_str = address_str.as_ref();
51    if !address_str.starts_with("0x") {
52        return Err(anyhow!("address must start with '0x'"));
53    }
54    Ok(address_str[2..].parse()?)
55}
56
57/// Performs an HTTP GET request and return the contents of the response.
58#[cfg(feature = "http")]
59pub fn http_get(url: &str) -> Result<String> {
60    let mut buffer = Vec::new();
61    let mut handle = Easy::new();
62    handle.url(url)?;
63    {
64        let mut transfer = handle.transfer();
65        transfer.write_function(|data| {
66            buffer.extend_from_slice(data);
67            Ok(data.len())
68        })?;
69        transfer.perform()?;
70    }
71
72    let buffer = String::from_utf8(buffer)?;
73    Ok(buffer)
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn input_name_to_ident_empty() {
82        assert_quote!(expand_input_name(0, ""), { p0 });
83    }
84
85    #[test]
86    fn input_name_to_ident_keyword() {
87        assert_quote!(expand_input_name(0, "self"), { self_ });
88    }
89
90    #[test]
91    fn input_name_to_ident_snake_case() {
92        assert_quote!(expand_input_name(0, "CamelCase1"), { camel_case_1 });
93    }
94
95    #[test]
96    fn parse_address_missing_prefix() {
97        assert!(
98            parse_address("0000000000000000000000000000000000000000").is_err(),
99            "parsing address not starting with 0x should fail"
100        );
101    }
102
103    #[test]
104    fn parse_address_address_too_short() {
105        assert!(
106            parse_address("0x00000000000000").is_err(),
107            "parsing address not starting with 0x should fail"
108        );
109    }
110
111    #[test]
112    fn parse_address_ok() {
113        let expected = Address::from([
114            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
115        ]);
116        assert_eq!(
117            parse_address("0x000102030405060708090a0b0c0d0e0f10111213").unwrap(),
118            expected
119        );
120    }
121}