Drying out some Actix-web code

I have a bit of code here that looks really WET (especially given that I kinda want to add a bunch of these in here); it seems like generics really cause problems for me like this one. Is there a general approach to make this more DRY?

    let logging_on = std::env::var("BENCH_WITH_LOGGING_ON").unwrap_or_else(|_| "".to_string());

    match logging_on.as_str() {
        "1" | "on" | "true" | "t" | "y" | "yes" => HttpServer::new(|| {
            App::new()
                .wrap(TracingLogger::default())
                .service(locations)
                .service(health_check)
                .service(health_ready)
        })
        .bind("127.0.0.1:8080")?
        .run()
        .await
        .unwrap(),
        _ => HttpServer::new(|| {
            App::new()
                .service(locations)
                .service(health_check)
                .service(health_ready)
        })
        .bind("127.0.0.1:8080")?
        .run()
        .await
        .unwrap(),
    };

It feels awful to have to capture two entire stack calls for what ought to be a couple of lines:

   if env_var is set
       then have the server add a tracing logger

macro_rules! to the rescue:

macro_rules! server {
  ($($marker:tt)?) => {
    HttpServer::new(|| {
      App::new()
        $(
          .wrap({
            // discard and return the logger
            stringify!($marker);
            TracingLogger::default()
          })
        )?
        .service(locations)
        .service(health_check)
        .service(health_ready)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
    .unwrap()
  }
}

Which turns all of your code in a simple:

match logging_on.as_str() {
    "1" | "on" | "true" | 
    "t" | "y" | "yes" 
      => server!(with_log),
    _else => server!(),
};
1 Like

I hadn't thought about macros. Thanks so much!

1 Like

For reference, in case some one else stumbles over this:

fn is_true(value: &str) -> Option<bool> {
    match value {
        "1" | "on" | "true" | "t" | "y" | "yes" => Some(true),
        "0" | "off" | "false" | "f" | "n" | "no" => Some(false),
        _ => None,
    }
}

macro_rules! server {
    ( $( $marker:expr ),* ) => {
      HttpServer::new(move || {
        App::new()
          $(
            .wrap($marker)
          )*
          .service(locations)
          .service(health_check)
          .service(health_ready)
      })
      .bind("127.0.0.1:8080")?
      .run()
      .await
      .unwrap()
    }
  }

and now...

    let with_log = std::env::var("BENCH_WITH_LOGGING_ON").unwrap_or_else(|_| "".to_string());
    let with_compression = std::env::var("BENCH_WITH_COMPRESSION_ON").unwrap_or_else(|_| "".to_string());

    match (with_log.as_str(), with_compression.as_str()) {
        (x, y) if is_true(x) == Some(true) && is_true(y) == Some(true) => server!(TracingLogger::default(), middleware::Compress::default()),
        (x, y) if is_true(x) != Some(true) && is_true(y) == Some(true) => server!(middleware::Compress::default()),
        (x, y) if is_true(x) == Some(true) && is_true(y) != Some(true) => server!(TracingLogger::default()),
        _else => server!(),
    };
1 Like

You could simplify that a bit more if you'd like

fn is_true(value: impl AsRef<str>) -> Option<bool> {
    match value.as_ref() {
        "1" | "on" | "true" | "t" | "y" | "yes" => Some(true),
        "0" | "off" | "false" | "f" | "n" | "no" => Some(false),
        _ => None,
    }
}
let with_log = std::env::var("BENCH_WITH_LOGGING_ON").unwrap_or_default();
let with_compression = std::env::var("BENCH_WITH_COMPRESSION_ON").unwrap_or_default();

match (is_true(&with_log), is_true(&with_compression)) {
    (Some(true), Some(true))  => server!(TracingLogger::default(), middleware::Compress::default()),
    (Some(false), Some(true)) => server!(middleware::Compress::default()),
    (Some(true), Some(false)) => server!(TracingLogger::default())
     _else => server!(),
};
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.