Whether to use enum or trait for multiple authentication types on API adapter

I'm building an HTTP API database adapter that has an authentication component. Users can authenticate using password, federated login such as OAUTH, and JWT.

My initial design is something like the following:


pub enum AuthType {
    Password,
    Federated,
    JWT,
}

pub struct Adapter {
    pub client: reqwest::Client,
    pub username: String,
    pub password: Option<String>,
    pub token: Option<String>,
    pub auth_type: AuthType
}

impl Adapter
  pub fn authenticate(&mut self) {
    match self.auth_type {
      AuthType::Federated => authenticate_with_sso(self)
      AuthType::JWT => authenticate_with_token(self)
      AuthType::Password => authenticate_with_pw(self)
  }
}

Each of these functions would contain the specific logic necessary to return an authenticated connection.

Where I'm hung up is whether this authentication scheme should something more like a trait with struct implementations. It's unlikely that there will be any user-defined implementations of these authentication schemes which led me to the above, but I'm not sure which is a cleaner approach.

The other could be something like

pub trait Authenticator {
    fn authenticate(&mut Adapter) -> &mut Adapter;
}

struct FederatedAuth {
   pub sso_url: string,
   pub saml_token: String
}

impl Authenticator for FederatedAuth {
  pub fn authenticate(adap: &mut Adapter) -> &mut Adapter {
    // do things to get the OAuth token and update the adapter
  }

}

pub struct Adapter<A: Authenticator> {
  // credentials etc,
  pub authenticator: A: Authenticator,
  // or Box<dyn Authenticator>
}

This seems more cumbersome, but perhaps more flexible? I'm not unsure whether it'd be preferable to use static or dynamic dispatch in the adapter above as carrying generics around the code can become a burden. As an aside, users will know their authentication type when instantiating an adapter.

Some areas where I'm hung up are:

  • Is Authenticator justified in being its own thing, and if so, should it also be part of the Adapter, or a standalone component?
  • Is the Adapter responsible for its own authentication? i.e. should authenticate be a method, or a standalone function that returns a new, authenticated Adapter?

Most of the time, deciding between enums vs. traits boils down to a matter of how many variants will you have, besides API ergonomics.

Looking at your code snippet, I would say a trait fits better due to ergonomics.