From 32c49ef946230e50256ef0b86538fd9e27a0f485 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 28 May 2024 14:17:24 -0400 Subject: [PATCH 1/7] refactor api client --- src/api.rs | 109 +++++++++++++++++++--------- src/command/auth/info.rs | 7 +- src/command/auth/mod.rs | 2 +- src/command/deployments/accounts.rs | 9 ++- src/command/deployments/create.rs | 9 ++- src/command/deployments/delete.rs | 9 ++- src/command/deployments/describe.rs | 8 +- src/command/deployments/fork.rs | 9 ++- src/command/deployments/list.rs | 8 +- src/command/deployments/logs.rs | 13 +++- src/command/deployments/update.rs | 9 ++- src/command/teams/members.rs | 20 +++-- 12 files changed, 140 insertions(+), 72 deletions(-) diff --git a/src/api.rs b/src/api.rs index 01a5ec4..8ecb284 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,59 +1,98 @@ use graphql_client::Response; -use serde::{de::DeserializeOwned, Serialize}; +use reqwest::RequestBuilder; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use url::Url; -use crate::credential::Credentials; +use crate::{constant, credential::AccessToken}; #[derive(Debug, thiserror::Error)] -pub enum ApiError { +pub enum Error { #[error(transparent)] - ReqwestError(reqwest::Error), - #[error(transparent)] - CredentialsError(anyhow::Error), + ReqwestError(#[from] reqwest::Error), + #[error("Invalid token, authenticate with `slot auth login`")] + Unauthorized, } -pub struct ApiClient { - base_url: String, +#[derive(Debug)] +pub struct Client { + base_url: Url, client: reqwest::Client, + access_token: Option, } -impl ApiClient { +impl Client { pub fn new() -> Self { Self { - base_url: "https://api.cartridge.gg/query".to_string(), + access_token: None, client: reqwest::Client::new(), + base_url: Url::parse(constant::CARTRIDGE_API_URL).expect("valid url"), } } - pub async fn post( - &self, - body: &T, - ) -> Result, ApiError> { - let credentials = Credentials::load() - .map_err(|_| { - anyhow::anyhow!("Failed to load credentials. Login with `slot auth login`.") - }) - .map_err(ApiError::CredentialsError)?; - - let res = self - .client - .post(&self.base_url) - .header( - "Authorization", - format!("Bearer {}", credentials.access_token), - ) + pub fn new_with_token(token: AccessToken) -> Self { + let mut client = Self::new(); + client.set_token(token); + client + } + + pub fn set_token(&mut self, token: AccessToken) { + self.access_token = Some(token); + } + + pub async fn query(&self, body: &T) -> Result, Error> + where + R: DeserializeOwned, + T: Serialize + ?Sized, + { + let path = "/query"; + let token = self.access_token.as_ref().map(|t| t.token.as_str()); + + // TODO: return this as an error if token is None + let bearer = format!("Bearer {}", token.unwrap_or_default()); + + let response = self + .post(path) + .header("Authorization", bearer) .json(body) .send() - .await - .map_err(ApiError::ReqwestError)?; + .await?; + + if response.status() == 403 { + return Err(Error::Unauthorized); + } + + Ok(response.json().await?) + } - if res.status() == 403 { - return Err(ApiError::CredentialsError(anyhow::anyhow!( - "Invalid token, authenticate with `slot auth login`" - ))); + pub async fn oauth2(&self, code: &str) -> Result { + #[derive(Deserialize)] + struct OauthToken { + #[serde(rename(deserialize = "access_token"))] + token: String, + #[serde(rename(deserialize = "token_type"))] + r#type: String, } - let res: Response = res.json().await.map_err(ApiError::ReqwestError)?; + let path = "/oauth2/token"; + let form = [("code", code)]; + + let response = self.post(path).form(&form).send().await?; + let token: OauthToken = response.json().await?; + + Ok(AccessToken { + token: token.token, + r#type: token.r#type, + }) + } + + fn post(&self, path: &str) -> RequestBuilder { + let url = self.get_url(path); + self.client.post(url) + } - Ok(res) + fn get_url(&self, path: &str) -> Url { + let mut url = self.base_url.clone(); + url.path_segments_mut().unwrap().extend(path.split('/')); + url } } diff --git a/src/command/auth/info.rs b/src/command/auth/info.rs index 362a5f9..6cd834d 100644 --- a/src/command/auth/info.rs +++ b/src/command/auth/info.rs @@ -1,16 +1,15 @@ use anyhow::Result; use clap::Args; use graphql_client::{GraphQLQuery, Response}; +use me::{ResponseData, Variables}; -use crate::api::ApiClient; - -use self::me::{ResponseData, Variables}; +use crate::api::Client; #[derive(GraphQLQuery)] #[graphql( schema_path = "schema.json", query_path = "src/command/auth/info.graphql", - response_derives = "Debug" + response_derives = "Debug, Clone, Serialize" )] pub struct Me; diff --git a/src/command/auth/mod.rs b/src/command/auth/mod.rs index c5f0e5f..de175d2 100644 --- a/src/command/auth/mod.rs +++ b/src/command/auth/mod.rs @@ -3,7 +3,7 @@ use clap::Subcommand; use self::{info::InfoArgs, login::LoginArgs}; -mod info; +pub mod info; mod login; #[derive(Subcommand, Debug)] diff --git a/src/command/deployments/accounts.rs b/src/command/deployments/accounts.rs index 821f8c6..663c533 100644 --- a/src/command/deployments/accounts.rs +++ b/src/command/deployments/accounts.rs @@ -10,7 +10,8 @@ use katana_primitives::genesis::Genesis; use katana_primitives::genesis::{allocation::GenesisAccountAlloc, json::GenesisJson}; use std::str::FromStr; -use crate::api::ApiClient; +use crate::api::Client; +use crate::credential::Credentials; use super::services::KatanaAccountCommands; @@ -42,8 +43,10 @@ impl AccountsArgs { project: self.project.clone(), }); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors.clone() { for err in errors { println!("Error: {}", err.message); diff --git a/src/command/deployments/create.rs b/src/command/deployments/create.rs index c1bc3c0..292c753 100644 --- a/src/command/deployments/create.rs +++ b/src/command/deployments/create.rs @@ -5,12 +5,13 @@ use clap::Args; use graphql_client::{GraphQLQuery, Response}; use crate::{ - api::ApiClient, + api::Client, command::deployments::create::create_deployment::{ CreateDeploymentCreateDeployment::{KatanaConfig, MadaraConfig, ToriiConfig}, CreateKatanaConfigInput, CreateMadaraConfigInput, CreateServiceConfigInput, CreateServiceInput, CreateToriiConfigInput, DeploymentService, DeploymentTier, Variables, }, + credential::Credentials, }; use super::{services::CreateServiceCommands, Long, Tier}; @@ -104,8 +105,10 @@ impl CreateArgs { wait: Some(true), }); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors.clone() { for err in errors { println!("Error: {}", err.message); diff --git a/src/command/deployments/delete.rs b/src/command/deployments/delete.rs index ae42ace..a01771c 100644 --- a/src/command/deployments/delete.rs +++ b/src/command/deployments/delete.rs @@ -5,8 +5,9 @@ use clap::Args; use graphql_client::{GraphQLQuery, Response}; use crate::{ - api::ApiClient, + api::Client, command::deployments::delete::delete_deployment::{DeploymentService, Variables}, + credential::Credentials, }; #[derive(GraphQLQuery)] @@ -47,8 +48,10 @@ impl DeleteArgs { service, }); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors.clone() { for err in errors { println!("Error: {}", err.message); diff --git a/src/command/deployments/describe.rs b/src/command/deployments/describe.rs index 87233dc..3a57537 100644 --- a/src/command/deployments/describe.rs +++ b/src/command/deployments/describe.rs @@ -4,7 +4,7 @@ use anyhow::Result; use clap::Args; use graphql_client::{GraphQLQuery, Response}; -use crate::api::ApiClient; +use crate::{api::Client, credential::Credentials}; use self::describe_deployment::{ DeploymentService, @@ -47,8 +47,10 @@ impl DescribeArgs { service, }); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors.clone() { for err in errors { println!("Error: {}", err.message); diff --git a/src/command/deployments/fork.rs b/src/command/deployments/fork.rs index 50af5cb..056920b 100644 --- a/src/command/deployments/fork.rs +++ b/src/command/deployments/fork.rs @@ -7,10 +7,11 @@ use starknet::providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider}; use url::Url; use crate::{ - api::ApiClient, + api::Client, command::deployments::fork::fork_deployment::{ DeploymentTier, ForkDeploymentForkDeployment::KatanaConfig, Variables, }, + credential::Credentials, }; use super::{services::ForkServiceCommands, Long, Tier}; @@ -53,8 +54,10 @@ impl ForkArgs { wait: Some(true), }); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors.clone() { for err in errors { println!("Error: {}", err.message); diff --git a/src/command/deployments/list.rs b/src/command/deployments/list.rs index 11aaffa..b8a5c93 100644 --- a/src/command/deployments/list.rs +++ b/src/command/deployments/list.rs @@ -4,7 +4,7 @@ use anyhow::Result; use clap::Args; use graphql_client::{GraphQLQuery, Response}; -use crate::api::ApiClient; +use crate::{api::Client, credential::Credentials}; use self::list_deployments::{ResponseData, Variables}; @@ -24,8 +24,10 @@ impl ListArgs { pub async fn run(&self) -> Result<()> { let request_body = ListDeployments::build_query(Variables {}); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors.clone() { for err in errors { println!("Error: {}", err.message); diff --git a/src/command/deployments/logs.rs b/src/command/deployments/logs.rs index a90ca56..1bafa68 100644 --- a/src/command/deployments/logs.rs +++ b/src/command/deployments/logs.rs @@ -12,7 +12,10 @@ use clap::Args; use graphql_client::{GraphQLQuery, Response}; use tokio::time::sleep; -use crate::{api::ApiClient, command::deployments::logs::deployment_logs::DeploymentService}; +use crate::{ + api::Client, command::deployments::logs::deployment_logs::DeploymentService, + credential::Credentials, +}; use self::deployment_logs::{DeploymentLogsDeploymentLogs, ResponseData, Variables}; @@ -66,15 +69,17 @@ impl LogsArgs { } pub struct LogReader { - client: ApiClient, + client: Client, service: Service, project: String, } impl LogReader { pub fn new(service: Service, project: String) -> Self { + let user = Credentials::load().unwrap(); + let client = Client::new_with_token(user.access_token); LogReader { - client: ApiClient::new(), + client, service, project, } @@ -98,7 +103,7 @@ impl LogReader { limit: Some(limit), }); - let res: Response = self.client.post(&request_body).await?; + let res: Response = self.client.query(&request_body).await?; if let Some(errors) = res.errors { let error_message = errors .into_iter() diff --git a/src/command/deployments/update.rs b/src/command/deployments/update.rs index 8c8008a..2a5b1a1 100644 --- a/src/command/deployments/update.rs +++ b/src/command/deployments/update.rs @@ -6,12 +6,13 @@ use graphql_client::{GraphQLQuery, Response}; use self::update_deployment::UpdateServiceInput; use crate::{ - api::ApiClient, + api::Client, command::deployments::update::update_deployment::{ DeploymentService, DeploymentTier, UpdateDeploymentUpdateDeployment::{KatanaConfig, MadaraConfig, ToriiConfig}, UpdateKatanaConfigInput, UpdateServiceConfigInput, Variables, }, + credential::Credentials, }; use super::services::UpdateServiceCommands; @@ -79,8 +80,10 @@ impl UpdateArgs { wait: Some(true), }); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors.clone() { for err in errors { println!("Error: {}", err.message); diff --git a/src/command/teams/members.rs b/src/command/teams/members.rs index f4b8404..1969a33 100644 --- a/src/command/teams/members.rs +++ b/src/command/teams/members.rs @@ -2,7 +2,7 @@ use anyhow::Result; use clap::Args; use graphql_client::{GraphQLQuery, Response}; -use crate::api::ApiClient; +use crate::{api::Client, credential::Credentials}; #[derive(GraphQLQuery)] #[graphql( @@ -21,8 +21,10 @@ impl TeamListArgs { let request_body = TeamMembersList::build_query(self::team_members_list::Variables { team: team.clone() }); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors.clone() { for err in errors { println!("Error: {}", err.message); @@ -70,8 +72,10 @@ impl TeamAddArgs { accounts: self.account.clone(), }); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors { for err in errors { println!("Error: {}", err.message); @@ -106,8 +110,10 @@ impl TeamRemoveArgs { accounts: self.account.clone(), }); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let user = Credentials::load()?; + let client = Client::new_with_token(user.access_token); + + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors { for err in errors { println!("Error: {}", err.message); From 119024227de490185eb64e5ce53fb0f14caed521 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 28 May 2024 14:17:41 -0400 Subject: [PATCH 2/7] save account info in credentials.json --- src/command/auth/info.graphql | 7 ++++- src/command/auth/info.rs | 5 ++-- src/credential.rs | 55 ++++++++++++++++++++++++----------- src/server.rs | 36 +++++++++++++++++------ 4 files changed, 74 insertions(+), 29 deletions(-) diff --git a/src/command/auth/info.graphql b/src/command/auth/info.graphql index e04f54d..fbd519e 100644 --- a/src/command/auth/info.graphql +++ b/src/command/auth/info.graphql @@ -1,7 +1,12 @@ query Me { me { id - contractAddress name + contractAddress + credentials { + webauthn { + publicKey + } + } } } diff --git a/src/command/auth/info.rs b/src/command/auth/info.rs index 6cd834d..41c59d6 100644 --- a/src/command/auth/info.rs +++ b/src/command/auth/info.rs @@ -17,11 +17,12 @@ pub struct Me; pub struct InfoArgs {} impl InfoArgs { + // TODO: find the account info from `credentials.json` first before making a request pub async fn run(&self) -> Result<()> { let request_body = Me::build_query(Variables {}); - let client = ApiClient::new(); - let res: Response = client.post(&request_body).await?; + let client = Client::new(); + let res: Response = client.query(&request_body).await?; if let Some(errors) = res.errors { for err in errors { println!("Error: {}", err.message); diff --git a/src/credential.rs b/src/credential.rs index d884dd4..028988f 100644 --- a/src/credential.rs +++ b/src/credential.rs @@ -1,30 +1,51 @@ use serde::{Deserialize, Serialize}; -use std::fs::{self, OpenOptions}; -use std::io::{self, Read, Write}; +use std::io::{self}; +use std::path::PathBuf; -#[derive(Serialize, Deserialize)] +use crate::command::auth::info::me::MeMe; + +const SLOT_DIR: &str = "slot"; +const CREDENTIALS_FILE: &str = "credentials.json"; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AccessToken { + pub token: String, + pub r#type: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Credentials { - pub access_token: String, - pub token_type: String, + #[serde(flatten)] + pub account: Option, + pub access_token: AccessToken, } impl Credentials { + pub fn new(account: Option, access_token: AccessToken) -> Self { + Self { + account, + access_token, + } + } + pub fn load() -> io::Result { - let mut path = dirs::config_local_dir().unwrap(); - path.push("slot/credentials.json"); - let mut file = OpenOptions::new().read(true).open(&path)?; - let mut contents = String::new(); - file.read_to_string(&mut contents)?; - let credentials: Credentials = serde_json::from_str(&contents)?; + let path = get_file_path(); + let content = std::fs::read_to_string(path)?; + let credentials = serde_json::from_str(&content)?; Ok(credentials) } pub fn write(&self) -> io::Result<()> { - let mut path = dirs::config_local_dir().unwrap(); - path.push("slot/credentials.json"); - fs::create_dir_all(path.parent().unwrap())?; - let mut file = OpenOptions::new().write(true).create(true).open(&path)?; - let serialized = serde_json::to_string(self)?; - file.write_all(serialized.as_bytes()) + let path = get_file_path(); + let content = serde_json::to_string_pretty(&self)?; + std::fs::write(path, content)?; + Ok(()) } } + +/// Get the path to the credentials file. +fn get_file_path() -> PathBuf { + let mut path = dirs::config_local_dir().unwrap(); + path.extend([SLOT_DIR, CREDENTIALS_FILE]); + path +} diff --git a/src/server.rs b/src/server.rs index 9eb1a2d..1ed64be 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,6 +6,7 @@ use axum::{ routing::get, Router, }; +use graphql_client::GraphQLQuery; use log::error; use serde::Deserialize; use std::{ @@ -14,7 +15,15 @@ use std::{ }; use tokio::sync::mpsc::{Receiver, Sender}; -use crate::{constant, credential::Credentials}; +use crate::{ + api::Client, + command::auth::info::{ + me::{ResponseData, Variables}, + Me, + }, + constant, + credential::Credentials, +}; pub struct LocalServer { router: Router, @@ -65,17 +74,26 @@ impl<'a> LocalServer { // 2. Get access token using the authorization code match payload.code { Some(code) => { - let client = reqwest::Client::new(); - let response = client - .post(format!("{}oauth2/token", constant::CARTRIDGE_API_URL)) - .form(&[("code", &code)]) - .send() - .await?; + let mut api = Client::new(); + + let token = api.oauth2(&code).await?; + api.set_token(token.clone()); + + // fetch the account information + let request_body = Me::build_query(Variables {}); + let res: graphql_client::Response = api.query(&request_body).await?; + + // display the errors if any, but still process bcs we have the token + if let Some(errors) = res.errors { + for err in errors { + eprintln!("Error: {}", err.message); + } + } - let cred: Credentials = response.json().await?; + let account_info = res.data.map(|data| data.me.expect("should exist")); // 3. Store the access token locally - cred.write()?; + Credentials::new(account_info, token).write()?; println!("You are now logged in!\n"); From e53def43c565c4d36ff5d008c3c40fbdade130ec Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 28 May 2024 14:25:37 -0400 Subject: [PATCH 3/7] load token for auth info --- src/command/auth/info.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/command/auth/info.rs b/src/command/auth/info.rs index 41c59d6..b63071e 100644 --- a/src/command/auth/info.rs +++ b/src/command/auth/info.rs @@ -3,7 +3,7 @@ use clap::Args; use graphql_client::{GraphQLQuery, Response}; use me::{ResponseData, Variables}; -use crate::api::Client; +use crate::{api::Client, credential::Credentials}; #[derive(GraphQLQuery)] #[graphql( @@ -19,10 +19,12 @@ pub struct InfoArgs {} impl InfoArgs { // TODO: find the account info from `credentials.json` first before making a request pub async fn run(&self) -> Result<()> { - let request_body = Me::build_query(Variables {}); + let credentials = Credentials::load()?; + let client = Client::new_with_token(credentials.access_token); - let client = Client::new(); + let request_body = Me::build_query(Variables {}); let res: Response = client.query(&request_body).await?; + if let Some(errors) = res.errors { for err in errors { println!("Error: {}", err.message); From cd0745d559ab318bc8aef375d33dbbcbb72acf88 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 28 May 2024 15:39:54 -0400 Subject: [PATCH 4/7] create path on write --- src/credential.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/credential.rs b/src/credential.rs index 028988f..6ef7e7a 100644 --- a/src/credential.rs +++ b/src/credential.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use std::fs; use std::io::{self}; use std::path::PathBuf; @@ -30,15 +31,18 @@ impl Credentials { pub fn load() -> io::Result { let path = get_file_path(); - let content = std::fs::read_to_string(path)?; + let content = fs::read_to_string(path)?; let credentials = serde_json::from_str(&content)?; Ok(credentials) } pub fn write(&self) -> io::Result<()> { + // create the path if it doesn't yet exist let path = get_file_path(); + fs::create_dir_all(&path)?; + let content = serde_json::to_string_pretty(&self)?; - std::fs::write(path, content)?; + fs::write(path, content)?; Ok(()) } } From 6b73eb82a598c57ed1e8d237f227a6600a613e16 Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 28 May 2024 15:45:38 -0400 Subject: [PATCH 5/7] update --- src/credential.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/credential.rs b/src/credential.rs index 6ef7e7a..08c290b 100644 --- a/src/credential.rs +++ b/src/credential.rs @@ -37,9 +37,9 @@ impl Credentials { } pub fn write(&self) -> io::Result<()> { - // create the path if it doesn't yet exist + // create the dir if it doesn't yet exist let path = get_file_path(); - fs::create_dir_all(&path)?; + fs::create_dir_all(&path.parent().expect("qed; parent exist"))?; let content = serde_json::to_string_pretty(&self)?; fs::write(path, content)?; From b8e722495c3863728dc09ae22b378f17424e7ecf Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 28 May 2024 15:47:26 -0400 Subject: [PATCH 6/7] update --- src/credential.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/credential.rs b/src/credential.rs index 08c290b..d252743 100644 --- a/src/credential.rs +++ b/src/credential.rs @@ -37,7 +37,7 @@ impl Credentials { } pub fn write(&self) -> io::Result<()> { - // create the dir if it doesn't yet exist + // create the dir paths if it doesn't yet exist let path = get_file_path(); fs::create_dir_all(&path.parent().expect("qed; parent exist"))?; From c01645eee3520613002b23a3c794deac35c6923a Mon Sep 17 00:00:00 2001 From: Ammar Arif Date: Tue, 28 May 2024 16:05:15 -0400 Subject: [PATCH 7/7] clippy --- src/credential.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/credential.rs b/src/credential.rs index d252743..3c96dce 100644 --- a/src/credential.rs +++ b/src/credential.rs @@ -39,7 +39,7 @@ impl Credentials { pub fn write(&self) -> io::Result<()> { // create the dir paths if it doesn't yet exist let path = get_file_path(); - fs::create_dir_all(&path.parent().expect("qed; parent exist"))?; + fs::create_dir_all(path.parent().expect("qed; parent exist"))?; let content = serde_json::to_string_pretty(&self)?; fs::write(path, content)?;