derive-env-url 2.0.0

Derives for env-url
Documentation
extern crate darling;
extern crate syn;

use darling::{FromDeriveInput, FromMeta};
use quote::quote;
use std::vec;
use syn::{parse_macro_input, DeriveInput, Lit};
use url::Url;

struct ServiceUrl(Url);

impl FromMeta for ServiceUrl {
  fn from_value(value: &Lit) -> Result<Self, darling::Error> {
    if let Lit::Str(ref lit_str) = *value {
      match Url::parse(lit_str.value().as_str()) {
        Ok(url) => Ok(ServiceUrl(url)),
        Err(err) => Err(darling::Error::custom(err).with_span(&lit_str.span())),
      }
    } else {
      Err(darling::Error::unexpected_lit_type(value))
    }
  }
}

#[derive(FromDeriveInput)]
#[darling(attributes(env_url))]
struct ServiceURLOpt {
  env_prefix: String,
  default: ServiceUrl,
}

#[proc_macro_derive(EnvURL, attributes(env_url))]
pub fn derive_service_uri(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
  let input = parse_macro_input!(input as DeriveInput);
  let ident = &input.ident;

  let ServiceURLOpt {
    env_prefix,
    default: ServiceUrl(url),
  } = match FromDeriveInput::from_derive_input(&input) {
    Ok(attr) => attr,
    Err(err) => {
      return err.write_errors().into();
    }
  };

  let prefixed_url_env_key = format!("{}_URL_ENV", env_prefix);
  let prefixed_url_key = format!("{}_URL", env_prefix);
  let prefixed_scheme_env_key = format!("{}_SCHEMA_ENV", env_prefix);
  let prefixed_scheme_key = format!("{}_SCHEMA_ENV", env_prefix);
  let prefixed_host_env_key = format!("{}_HOST_ENV", env_prefix);
  let prefixed_host_key = format!("{}_HOST", env_prefix);
  let prefixed_port_env_key = format!("{}_PORT_ENV", env_prefix);
  let prefixed_port_key = format!("{}_PORT", env_prefix);
  let prefixed_path_env_key = format!("{}_PATH_ENV", env_prefix);
  let prefixed_path_key = format!("{}_PATH", env_prefix);
  let prefixed_query_env_key = format!("{}_QUERY_ENV", env_prefix);
  let prefixed_query_key = format!("{}_QUERY", env_prefix);
  let prefixed_username_env_key = format!("{}_USERNAME_ENV", env_prefix);
  let prefixed_username_key = format!("{}_USERNAME", env_prefix);
  let prefixed_password_env_key = format!("{}_PASSWORD_ENV", env_prefix);
  let prefixed_password_key = format!("{}_PASSWORD", env_prefix);

  let default_scheme = url.scheme();
  let default_host = url.host_str().unwrap();
  let default_port = match url.port() {
    Some(port) => port.to_string(),
    None => String::from(""),
  };

  let default_path = url.path();
  let default_query = url.query().unwrap_or("");
  let mut default_username = url.username();

  if default_username.is_empty() {
    default_username = "default";
  };

  let expanded = quote! {
    impl env_url::ServiceURL for #ident {
      fn service_url(&self) -> Result<env_url::url::Url, env_url::url::ParseError> {
        let service_url_env = std::env::var(#prefixed_url_env_key).unwrap_or_else(|_| String::from(#prefixed_url_key));

        let service_url = std::env::var(service_url_env).unwrap_or_else(|_| {
          let scheme = {
            let host_env =
              std::env::var(#prefixed_scheme_env_key).unwrap_or_else(|_| String::from(#prefixed_scheme_key));

            std::env::var(host_env).unwrap_or_else(|_| String::from(#default_scheme))
          };

          let host = {
            let host_env =
              std::env::var(#prefixed_host_env_key).unwrap_or_else(|_| String::from(#prefixed_host_key));

            std::env::var(host_env).unwrap_or_else(|_| String::from(#default_host))
          };

          let port = {
            let port_env = std::env::var(#prefixed_port_env_key).unwrap_or_else(|_| String::from(#prefixed_port_key));

            std::env::var(port_env).unwrap_or_else(|_| String::from(#default_port))
          };

          let path = {
            let path_env =
              std::env::var(#prefixed_path_env_key).unwrap_or_else(|_| String::from(#prefixed_path_key));

            std::env::var(path_env).unwrap_or_else(|_| String::from(#default_path))
          };

          let username = {
            let username_env =
              std::env::var(#prefixed_username_env_key).unwrap_or_else(|_| String::from(#prefixed_username_key));

            std::env::var(username_env).unwrap_or_else(|_| String::from(#default_username))
          };

          let password_env =
            std::env::var(#prefixed_password_env_key).unwrap_or_else(|_| String::from(#prefixed_password_key));

          let query = {
            let query_env =
              std::env::var(#prefixed_query_env_key).unwrap_or_else(|_| String::from(#prefixed_query_key));

            std::env::var(query_env).unwrap_or_else(|_| String::from(#default_query))
          };

          match (std::env::var(password_env), query.is_empty()) {
            (Ok(password), true) =>  format!(
              "{}://{}:{}@{}:{}{}",
              scheme, username, password, host, port, path
            ),
            (Ok(password), false) => format!(
              "{}://{}:{}@{}:{}{}?{}",
              scheme, username, password, host, port, path, query
            ),
            (Err(_), true) => format!("{}://{}:{}{}", scheme, host, port, path),
            (Err(_), false) => format!("{}://{}:{}{}?{}", scheme, host, port, path, query)
          }
        });

        env_url::Url::parse(&service_url)
      }
    }
  };

  expanded.into()
}