derive_env_url/
lib.rs

1extern crate darling;
2extern crate syn;
3
4use darling::{FromDeriveInput, FromMeta};
5use quote::quote;
6use syn::{parse_macro_input, DeriveInput, Lit};
7use url::Url;
8
9struct ServiceUrl(Url);
10
11impl FromMeta for ServiceUrl {
12  fn from_value(value: &Lit) -> Result<Self, darling::Error> {
13    if let Lit::Str(ref lit_str) = *value {
14      match Url::parse(lit_str.value().as_str()) {
15        Ok(url) => Ok(ServiceUrl(url)),
16        Err(err) => Err(darling::Error::custom(err).with_span(&lit_str.span())),
17      }
18    } else {
19      Err(darling::Error::unexpected_lit_type(value))
20    }
21  }
22}
23
24#[derive(FromDeriveInput)]
25#[darling(attributes(env_url))]
26struct ServiceURLOpt {
27  env_prefix: String,
28  default: ServiceUrl,
29}
30
31#[proc_macro_derive(EnvURL, attributes(env_url))]
32pub fn derive_service_uri(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
33  let input = parse_macro_input!(input as DeriveInput);
34  let ident = &input.ident;
35
36  let ServiceURLOpt {
37    env_prefix,
38    default: ServiceUrl(url),
39  } = match FromDeriveInput::from_derive_input(&input) {
40    Ok(attr) => attr,
41    Err(err) => {
42      return err.write_errors().into();
43    }
44  };
45
46  let prefixed_url_env_key = format!("{}_URL_ENV", env_prefix);
47  let prefixed_url_key = format!("{}_URL", env_prefix);
48  let prefixed_scheme_env_key = format!("{}_SCHEMA_ENV", env_prefix);
49  let prefixed_scheme_key = format!("{}_SCHEMA_ENV", env_prefix);
50  let prefixed_host_env_key = format!("{}_HOST_ENV", env_prefix);
51  let prefixed_host_key = format!("{}_HOST", env_prefix);
52  let prefixed_port_env_key = format!("{}_PORT_ENV", env_prefix);
53  let prefixed_port_key = format!("{}_PORT", env_prefix);
54  let prefixed_path_env_key = format!("{}_PATH_ENV", env_prefix);
55  let prefixed_path_key = format!("{}_PATH", env_prefix);
56  let prefixed_query_env_key = format!("{}_QUERY_ENV", env_prefix);
57  let prefixed_query_key = format!("{}_QUERY", env_prefix);
58  let prefixed_userinfo_env_key = format!("{}_USERINFO_ENV", env_prefix);
59  let prefixed_userinfo_key = format!("{}_USERINFO", env_prefix);
60
61  let default_scheme = url.scheme();
62  let default_host = url.host_str().unwrap();
63  let default_port = match url.port() {
64    Some(port) => port.to_string(),
65    None => String::from(""),
66  };
67
68  let default_path = url.path();
69  let default_query = url.query().unwrap_or("");
70
71  let default_userinfo = match url.password() {
72    Some(password) => format!("{}:{}", url.username(), password),
73    None => String::from(url.username()),
74  };
75
76  let expanded = quote! {
77    impl env_url::ServiceURL for #ident {
78      fn service_url() -> Result<env_url::url::Url, env_url::url::ParseError> {
79        let service_url_env = std::env::var(#prefixed_url_env_key).unwrap_or_else(|_| String::from(#prefixed_url_key));
80
81        let service_url = std::env::var(service_url_env).unwrap_or_else(|_| {
82          let scheme = {
83            let host_env =
84              std::env::var(#prefixed_scheme_env_key).unwrap_or_else(|_| String::from(#prefixed_scheme_key));
85
86            std::env::var(host_env).unwrap_or_else(|_| String::from(#default_scheme))
87          };
88
89          let host = {
90            let host_env =
91              std::env::var(#prefixed_host_env_key).unwrap_or_else(|_| String::from(#prefixed_host_key));
92
93            std::env::var(host_env).unwrap_or_else(|_| String::from(#default_host))
94          };
95
96          let port = {
97            let port_env = std::env::var(#prefixed_port_env_key).unwrap_or_else(|_| String::from(#prefixed_port_key));
98
99            std::env::var(port_env).unwrap_or_else(|_| String::from(#default_port))
100          };
101
102          let path = {
103            let path_env =
104              std::env::var(#prefixed_path_env_key).unwrap_or_else(|_| String::from(#prefixed_path_key));
105
106            std::env::var(path_env).unwrap_or_else(|_| String::from(#default_path))
107          };
108
109          let userinfo = {
110            let userinfo_env =
111              std::env::var(#prefixed_userinfo_env_key).unwrap_or_else(|_| String::from(#prefixed_userinfo_key));
112
113            std::env::var(userinfo_env).unwrap_or_else(|_| String::from(#default_userinfo))
114          };
115
116          let query = {
117            let query_env =
118              std::env::var(#prefixed_query_env_key).unwrap_or_else(|_| String::from(#prefixed_query_key));
119
120            std::env::var(query_env).unwrap_or_else(|_| String::from(#default_query))
121          };
122
123          match (userinfo.is_empty(), query.is_empty()) {
124            (false, true) =>  format!(
125              "{}://{}@{}:{}{}",
126              scheme, userinfo, host, port, path
127            ),
128            (false, false) => format!(
129              "{}://{}@{}:{}{}?{}",
130              scheme, userinfo, host, port, path, query
131            ),
132            (true, true) => format!("{}://{}:{}{}", scheme, host, port, path),
133            (true, false) => format!("{}://{}:{}{}?{}", scheme, host, port, path, query)
134          }
135        });
136
137        env_url::Url::parse(&service_url)
138      }
139    }
140  };
141
142  expanded.into()
143}