use clap::{Args, ArgGroup}; use restson::RestClient; use anyhow::{Result, Error}; use std::str::{FromStr}; #[derive(Args, Debug)] #[command(group( ArgGroup::new("authentication") .args(["access", "refresh", "email", "phone"]) ))] pub struct Authentication { #[arg(long)] access: Option, #[arg(long)] refresh: Option, #[arg(long)] email: Option, #[arg(long)] phone: Option, } fn bearer(mut client: RestClient, token: &str) -> Result { client.set_header("Authorization", &format!("Bearer {}", token))?; Ok(client) } impl Authentication { pub async fn apply(&self, client: RestClient) -> Result { let client = match (self.access.as_ref(), self.refresh.as_ref(), self.email.as_ref(), self.phone.as_ref()) { (Some(ref v), None, None, None) => v.apply(client)?, (None, Some(ref v), None, None) => v.apply(client).await?, (None, None, Some(ref v), None) => v.apply(client).await?, (None, None, None, Some(ref v)) => v.apply(client).await?, (None, None, None, None) => client, (a, b, c, d) => anyhow::bail!("invalid authentication: {} + {} + {} + {}", a.is_some(), b.is_some(), c.is_some(), d.is_some()), }; Ok(client) } } #[derive(Debug, Clone)] struct WithPassword { value: String, password: String, } impl FromStr for WithPassword { type Err = Error; fn from_str(s: &str) -> Result { let password = match std::env::var("SPOND_PASSWORD") { Ok(password) => password, Err(_) => rpassword::prompt_password("Password: ")?, }; let value = String::from_str(s)?; Ok(Self { value, password }) } } #[derive(Debug, Clone)] struct Email(WithPassword); impl Email { async fn apply(&self, client: RestClient) -> Result { let tokens = spond_api::authentication::email(&client, &self.0.value, &self.0.password).await?; bearer(client, tokens.access.token.as_ref()) } } impl FromStr for Email { type Err= ::Err; fn from_str(s: &str) -> Result { Ok(Self(WithPassword::from_str(s)?)) } } #[derive(Debug, Clone)] struct Phone(WithPassword); impl Phone { async fn apply(&self, client: RestClient) -> Result { let tokens = spond_api::authentication::phone(&client, &self.0.value, &self.0.password).await?; bearer(client, tokens.access.token.as_ref()) } } impl FromStr for Phone { type Err= ::Err; fn from_str(s: &str) -> Result { Ok(Self(WithPassword::from_str(s)?)) } } #[derive(Debug, Clone)] struct Access(String); impl Access { fn apply(&self, client: RestClient) -> Result { bearer(client, &self.0) } } impl FromStr for Access { type Err = std::convert::Infallible; // parsing a String never fails fn from_str(s: &str) -> Result { Ok(Access(s.to_string())) } } #[derive(Debug, Clone)] struct Refresh(String); impl Refresh { async fn apply(&self, client: RestClient) -> Result { let tokens = spond_api::authentication::token(&client, &self.0).await?; bearer(client, tokens.access.token.as_ref()) } } impl FromStr for Refresh { type Err = std::convert::Infallible; // parsing a String never fails fn from_str(s: &str) -> Result { Ok(Refresh(s.to_string())) } }