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}