From de33c69484c0fd45f7ae8ca11c94495a22aa3899 Mon Sep 17 00:00:00 2001 From: curtain Date: Fri, 24 Feb 2023 16:26:30 +0800 Subject: [PATCH] feat: refactor config --- Cargo.toml | 2 +- src/cache/mod.rs | 34 ++--- src/cfg.rs | 311 +++++++++++++++++++--------------------- src/cmds/data.rs | 4 +- src/err.rs | 34 +++-- src/helper.rs | 17 ++- src/plugins/chrome.rs | 6 +- src/plugins/leetcode.rs | 49 ++----- 8 files changed, 213 insertions(+), 244 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5da9c82..94b5bd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,6 @@ async-trait = "0.1.56" tokio = { version = "1.19.2", features = ["full"] } clap = { version = "4", features = ["cargo"] } colored = "2.0.0" -dirs = "4.0.0" env_logger = "0.9.0" keyring = "1.2.0" log = "0.4.17" @@ -32,6 +31,7 @@ serde_json = "1.0.82" toml = "0.5.9" regex = "1.6.0" scraper = "0.13.0" +etcetera = "0.4.0" [dependencies.diesel] version = "1.4.8" diff --git a/src/cache/mod.rs b/src/cache/mod.rs index 6ec86cb..61d431a 100644 --- a/src/cache/mod.rs +++ b/src/cache/mod.rs @@ -7,7 +7,8 @@ use self::models::*; use self::schemas::{problems::dsl::*, tags::dsl::*}; use self::sql::*; use crate::helper::test_cases_path; -use crate::{cfg, err::Error, plugins::LeetCode}; +use crate::Config; +use crate::{err::Error, plugins::LeetCode}; use colored::Colorize; use diesel::prelude::*; use reqwest::Response; @@ -40,12 +41,12 @@ pub struct Cache(pub LeetCode); impl Cache { /// Ref to sqlite connection fn conn(&self) -> Result { - Ok(conn(self.0.conf.storage.cache()?)) + Ok(conn(Config::problems_filepath()?)) } /// Clean cache pub fn clean(&self) -> Result<(), Error> { - Ok(std::fs::remove_file(&self.0.conf.storage.cache()?)?) + Ok(std::fs::remove_file(Config::problems_filepath()?)?) } /// ref to download probems @@ -55,7 +56,7 @@ impl Cache { } pub fn update_after_ac(self, rid: i32) -> Result<(), Error> { - let c = conn(self.0.conf.storage.cache()?); + let c = conn(Config::problems_filepath()?); let target = problems.filter(id.eq(rid)); diesel::update(target).set(status.eq("ac")).execute(&c)?; Ok(()) @@ -307,32 +308,16 @@ impl Cache { json.insert("data_input", test_case); let url = match run { - Run::Test => conf - .sys - .urls - .get("test") - .ok_or(Error::NoneError)? - .replace("$slug", &p.slug), + Run::Test => conf.sys.urls.test.replace("$slug", &p.slug), Run::Submit => { json.insert("judge_type", "large".to_string()); - conf.sys - .urls - .get("submit") - .ok_or(Error::NoneError)? - .replace("$slug", &p.slug) + conf.sys.urls.submit.replace("$slug", &p.slug) } }; Ok(( json, - [ - url, - conf.sys - .urls - .get("problems") - .ok_or(Error::NoneError)? - .replace("$slug", &p.slug), - ], + [url, conf.sys.urls.problems.replace("$slug", &p.slug)], )) } @@ -395,8 +380,7 @@ impl Cache { /// New cache pub fn new() -> Result { - let conf = cfg::locate()?; - let c = conn(conf.storage.cache()?); + let c = conn(Config::problems_filepath()?); diesel::sql_query(CREATE_PROBLEMS_IF_NOT_EXISTS).execute(&c)?; diesel::sql_query(CREATE_TAGS_IF_NOT_EXISTS).execute(&c)?; diff --git a/src/cfg.rs b/src/cfg.rs index 184e3e0..995ce90 100644 --- a/src/cfg.rs +++ b/src/cfg.rs @@ -3,103 +3,108 @@ //! leetcode-cli will generate a `leetcode.toml` by default, //! if you wanna change to it, you can: //! -//! + Edit leetcode.toml at `~/.leetcode/leetcode.toml` directly +//! + Inspect code and problems at `cache_dir` directory: +//! |Platform | Value | Example | +//! | ------- | ----------------------------------- | ---------------------------- | +//! | Unix | `$XDG_CACHE_HOME` or `$HOME`/.cache | /home/alice/.cache | +//! | Windows | `{FOLDERID_LocalAppData}` | C:\Users\Alice\AppData\Local | +//! +//! + Edit leetcode.toml at `config_dir` directly: +//! |Platform | Value | Example | +//! | ------- | ------------------------------------- | ---------------------------------------- | +//! | Unix | `$XDG_CONFIG_HOME` or `$HOME`/.config | /home/alice/.config | +//! | Windows | `{FOLDERID_RoamingAppData}` | C:\Users\Alice\AppData\Roaming | +//! //! + Use `leetcode config` to update it use crate::Error; +use etcetera::base_strategy::{choose_base_strategy, BaseStrategy}; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs, path::PathBuf}; - -pub const DEFAULT_CONFIG: &str = r##" -# usually you don't wanna change those -[sys] -categories = [ - "algorithms", - "concurrency", - "database", - "shell" -] - -langs = [ - "bash", - "c", - "cpp", - "csharp", - "golang", - "java", - "javascript", - "kotlin", - "mysql", - "php", - "python", - "python3", - "ruby", - "rust", - "scala", - "swift" -] - -[sys.urls] -base = "https://leetcode.com" -graphql = "https://leetcode.com/graphql" -login = "https://leetcode.com/accounts/login/" -problems = "https://leetcode.com/api/problems/$category/" -problem = "https://leetcode.com/problems/$slug/description/" -tag = "https://leetcode.com/tag/$slug/" -test = "https://leetcode.com/problems/$slug/interpret_solution/" -session = "https://leetcode.com/session/" -submit = "https://leetcode.com/problems/$slug/submit/" -submissions = "https://leetcode.com/api/submissions/$slug" -submission = "https://leetcode.com/submissions/detail/$id/" -verify = "https://leetcode.com/submissions/detail/$id/check/" -favorites = "https://leetcode.com/list/api/questions" -favorite_delete = "https://leetcode.com/list/api/questions/$hash/$id" - -[code] -editor = "vim" -lang = "rust" -edit_code_marker = false -comment_problem_desc = false -comment_leading = "///" -start_marker = "@lc code=start" -end_marker = "@lc code=start" -test = true -pick = "${fid}.${slug}" -submission = "${fid}.${slug}.${sid}.${ac}" - -[cookies] -csrf = "" -session = "" - -[storage] -root = "~/.leetcode" -scripts = "scripts" -code = "code" -# absolutely path for the cache, other use root as parent dir -cache = "~/.cache/leetcode" -"##; +use std::{fs, path::PathBuf}; /// Sync with `~/.leetcode/config.toml` -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Default, Clone, Debug, Deserialize, Serialize)] pub struct Config { pub sys: Sys, pub code: Code, pub cookies: Cookies, - pub storage: Storage, } impl Config { /// Sync new config to config.toml pub fn sync(&self) -> Result<(), Error> { - let home = dirs::home_dir().ok_or(Error::NoneError)?; - let conf = home.join(".leetcode/leetcode.toml"); + let conf = Self::config_dir().join("leetcode.toml"); fs::write(conf, toml::ser::to_string_pretty(&self)?)?; Ok(()) } + + pub fn home_dir() -> PathBuf { + etcetera::home_dir().expect("Unable to find the home directory!") + } + + /// config_dir for `leetcode.toml` + pub fn config_dir() -> PathBuf { + let strategy = choose_base_strategy().expect("Unable to find the config directory!"); + let mut path = strategy.config_dir(); + path.push("leetcode"); + path + } + + /// create `config_dir` if not exists, and serialize default `Config` to it. + pub fn config_content() -> Result { + let config_filepath = Self::config_dir(); + if !config_filepath.exists() { + fs::create_dir_all(&config_filepath)?; + } + let config_filepath = config_filepath.join("leetcode.toml"); + match fs::read_to_string(&config_filepath) { + Ok(s) => Ok(toml::from_str(&s)?), + Err(_) => { + // serialize default config + let def_config = Config::default(); + fs::write(config_filepath, toml::ser::to_string_pretty(&def_config)?)?; + Ok(def_config) + } + } + } + + pub fn script_dir_or_create() -> Result { + let script_dir = Self::config_dir().join("scripts"); + if !script_dir.exists() { + fs::create_dir_all(&script_dir)?; + } + Ok(script_dir.to_string_lossy().to_string()) + } + + /// cache_dir for `code` and `problems` + pub fn cache_dir() -> PathBuf { + let strategy = choose_base_strategy().expect("Unable to find the cache directory!"); + let mut path = strategy.cache_dir(); + path.push("leetcode"); + path + } + + /// problems filepath + pub fn problems_filepath() -> Result { + let cache_dir = Self::cache_dir(); + if !cache_dir.exists() { + fs::create_dir_all(&cache_dir)?; + } + Ok(cache_dir.join("problems").to_string_lossy().to_string()) + } + + /// cache `code` dir + pub fn code_dir_or_create() -> Result { + let code_dir = Self::cache_dir().join("code"); + if !code_dir.exists() { + fs::create_dir_all(&code_dir)?; + } + Ok(code_dir.to_string_lossy().to_string()) + } } /// Cookie settings -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Default, Clone, Debug, Deserialize, Serialize)] pub struct Cookies { pub csrf: String, pub session: String, @@ -110,7 +115,40 @@ pub struct Cookies { pub struct Sys { pub categories: Vec, pub langs: Vec, - pub urls: HashMap, + pub urls: Urls, +} + +impl Default for Sys { + fn default() -> Self { + Self { + categories: vec!["algorithms", "concurrency", "database", "shell"] + .into_iter() + .map(|s| s.into()) + .collect(), + langs: vec![ + "bash", + "c", + "cpp", + "csharp", + "golang", + "java", + "javascript", + "kotlin", + "mysql", + "php", + "python", + "python3", + "ruby", + "rust", + "scala", + "swift", + ] + .into_iter() + .map(|s| s.into()) + .collect(), + urls: Urls::default(), + } + } } /// Leetcode API @@ -121,6 +159,7 @@ pub struct Urls { pub login: String, pub problems: String, pub problem: String, + pub tag: String, pub test: String, pub session: String, pub submit: String, @@ -131,6 +170,27 @@ pub struct Urls { pub favorite_delete: String, } +impl Default for Urls { + fn default() -> Self { + Self { + base: "https://leetcode.com".into(), + graphql: "https://leetcode.com/graphql".into(), + login: "https://leetcode.com/accounts/login/".into(), + problems: "https://leetcode.com/api/problems/$category/".into(), + problem: "https://leetcode.com/problems/$slug/description/".into(), + tag: "https://leetcode.com/tag/$slug/".into(), + test: "https://leetcode.com/problems/$slug/interpret_solution/".into(), + session: "https://leetcode.com/session/".into(), + submit: "https://leetcode.com/problems/$slug/submit/".into(), + submissions: "https://leetcode.com/submissions/detail/$id/".into(), + submission: "https://leetcode.com/submissions/detail/$id/".into(), + verify: "https://leetcode.com/submissions/detail/$id/check/".into(), + favorites: "https://leetcode.com/list/api/questions".into(), + favorite_delete: "https://leetcode.com/list/api/questions/$hash/$id".into(), + } + } +} + /// default editor and langs /// /// + support editor: [emacs, vim] @@ -151,89 +211,20 @@ pub struct Code { pub submission: String, } -/// Locate code files -/// -/// + cache -> the path to cache -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct Storage { - cache: String, - code: String, - root: String, - scripts: Option, -} - -impl Storage { - /// convert root path - pub fn root(&self) -> Result { - let home = dirs::home_dir() - .ok_or(Error::NoneError)? - .to_string_lossy() - .to_string(); - let path = self.root.replace('~', &home); - Ok(path) - } - - /// get cache path - pub fn cache(&self) -> Result { - let home = dirs::home_dir() - .ok_or(Error::NoneError)? - .to_string_lossy() - .to_string(); - let path = PathBuf::from(self.cache.replace('~', &home)); - if !path.is_dir() { - info!("Generate cache dir at {:?}.", &path); - fs::DirBuilder::new().recursive(true).create(&path)?; - } - - Ok(path.join("Problems").to_string_lossy().to_string()) - } - - /// get code path - pub fn code(&self) -> Result { - let root = &self.root()?; - let p = PathBuf::from(root).join(&self.code); - if !PathBuf::from(&p).exists() { - fs::create_dir(&p)? - } - - Ok(p.to_string_lossy().to_string()) - } - - /// get scripts path - pub fn scripts(mut self) -> Result { - let root = &self.root()?; - if self.scripts.is_none() { - let tmp = toml::from_str::(DEFAULT_CONFIG)?; - self.scripts = Some(tmp.storage.scripts.ok_or(Error::NoneError)?); +impl Default for Code { + fn default() -> Self { + Self { + editor: "vim".into(), + editor_args: None, + edit_code_marker: false, + start_marker: "".into(), + end_marker: "".into(), + comment_problem_desc: false, + comment_leading: "///".into(), + test: true, + lang: "rust".into(), + pick: "${fid}.${slug}".into(), + submission: "${fid}.${slug}.${sid}.${ac}".into(), } - - let p = PathBuf::from(root).join(&self.scripts.ok_or(Error::NoneError)?); - if !PathBuf::from(&p).exists() { - std::fs::create_dir(&p)? - } - - Ok(p.to_string_lossy().to_string()) - } -} - -/// Locate lc's config file -pub fn locate() -> Result { - let conf = root()?.join("leetcode.toml"); - if !conf.is_file() { - fs::write(&conf, &DEFAULT_CONFIG[1..])?; } - - let s = fs::read_to_string(&conf)?; - Ok(toml::from_str::(&s)?) -} - -/// Get root path of leetcode-cli -pub fn root() -> Result { - let dir = dirs::home_dir().ok_or(Error::NoneError)?.join(".leetcode"); - if !dir.is_dir() { - info!("Generate root dir at {:?}.", &dir); - fs::DirBuilder::new().recursive(true).create(&dir)?; - } - - Ok(dir) } diff --git a/src/cmds/data.rs b/src/cmds/data.rs index cb66e39..d51f93d 100644 --- a/src/cmds/data.rs +++ b/src/cmds/data.rs @@ -1,6 +1,6 @@ //! Cache managger use super::Command; -use crate::{cache::Cache, helper::Digit, Error}; +use crate::{cache::Cache, helper::Digit, Config, Error}; use async_trait::async_trait; use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand}; use colored::Colorize; @@ -53,7 +53,7 @@ impl Command for DataCommand { use std::path::Path; let cache = Cache::new()?; - let path = cache.0.conf.storage.cache()?; + let path = Config::problems_filepath()?; let f = File::open(&path)?; let len = format!("{}K", f.metadata()?.len() / 1000); diff --git a/src/err.rs b/src/err.rs index 344cf1b..73aba7b 100644 --- a/src/err.rs +++ b/src/err.rs @@ -1,6 +1,8 @@ //! Errors in leetcode-cli -use crate::cfg::{root, DEFAULT_CONFIG}; -use crate::cmds::{Command, DataCommand}; +use crate::{ + cmds::{Command, DataCommand}, + Config, +}; use colored::Colorize; use std::fmt; @@ -102,17 +104,27 @@ impl std::convert::From for Error { // toml impl std::convert::From for Error { fn from(_err: toml::de::Error) -> Self { - let conf = root().unwrap().join("leetcode_tmp.toml"); - std::fs::write(&conf, &DEFAULT_CONFIG[1..]).unwrap(); + let conf = Config::config_dir().join("leetcode_tmp.toml"); + std::fs::write( + &conf, + toml::ser::to_string_pretty(&Config::default()).unwrap(), + ) + .unwrap(); #[cfg(debug_assertions)] let err_msg = format!( "{}, {}{}{}{}{}{}", _err, "Parse config file failed, ", "leetcode-cli has just generated a new leetcode.toml at ", - "~/.leetcode/leetcode_tmp.toml,".green().bold().underline(), + conf.to_str().unwrap().green().bold().underline(), " the current one at ", - "~/.leetcode/leetcode.toml".yellow().bold().underline(), + Config::config_dir() + .join("leetcode.toml") + .to_str() + .unwrap() + .yellow() + .bold() + .underline(), " seems missing some keys, Please compare the new file and add the missing keys.\n", ); #[cfg(not(debug_assertions))] @@ -120,9 +132,15 @@ impl std::convert::From for Error { "{}{}{}{}{}{}", "Parse config file failed, ", "leetcode-cli has just generated a new leetcode.toml at ", - "~/.leetcode/leetcode_tmp.toml,".green().bold().underline(), + conf.to_str().unwrap().green().bold().underline(), " the current one at ", - "~/.leetcode/leetcode.toml".yellow().bold().underline(), + Config::config_dir() + .join("leetcode.toml") + .to_str() + .unwrap() + .yellow() + .bold() + .underline(), " seems missing some keys, Please compare the new file and add the missing keys.\n", ); Error::ParseError(err_msg) diff --git a/src/helper.rs b/src/helper.rs index 60efcd8..1832b3b 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -186,12 +186,16 @@ mod file { } } - use crate::{cache::models::Problem, Error}; + use crate::{cache::models::Problem, Config, Error}; /// Generate test cases path by fid pub fn test_cases_path(problem: &Problem) -> Result { - let conf = crate::cfg::locate()?; - let mut path = format!("{}/{}.tests.dat", conf.storage.code()?, conf.code.pick); + let conf = Config::config_content()?; + let mut path = format!( + "{}/{}.tests.dat", + Config::code_dir_or_create()?, + conf.code.pick + ); path = path.replace("${fid}", &problem.fid.to_string()); path = path.replace("${slug}", &problem.slug.to_string()); @@ -200,7 +204,7 @@ mod file { /// Generate code path by fid pub fn code_path(problem: &Problem, l: Option) -> Result { - let conf = crate::cfg::locate()?; + let conf = Config::config_content()?; let mut lang = conf.code.lang; if l.is_some() { lang = l.ok_or(Error::NoneError)?; @@ -208,7 +212,7 @@ mod file { let mut path = format!( "{}/{}.{}", - conf.storage.code()?, + Config::code_dir_or_create()?, conf.code.pick, suffix(&lang)?, ); @@ -223,9 +227,8 @@ mod file { pub fn load_script(module: &str) -> Result { use std::fs::File; use std::io::Read; - let conf = crate::cfg::locate()?; let mut script = "".to_string(); - File::open(format!("{}/{}.py", conf.storage.scripts()?, module))? + File::open(format!("{}/{}.py", Config::script_dir_or_create()?, module))? .read_to_string(&mut script)?; Ok(script) diff --git a/src/plugins/chrome.rs b/src/plugins/chrome.rs index 8893999..159299b 100644 --- a/src/plugins/chrome.rs +++ b/src/plugins/chrome.rs @@ -1,4 +1,4 @@ -use crate::{cache, Error}; +use crate::{cache, Config, Error}; use diesel::prelude::*; use keyring::Entry; use openssl::{hash, pkcs5, symm}; @@ -41,7 +41,7 @@ impl std::string::ToString for Ident { /// Get cookies from chrome storage pub fn cookies() -> Result { - let ccfg = crate::cfg::locate()?.cookies; + let ccfg = Config::config_content()?.cookies; if !ccfg.csrf.is_empty() && !ccfg.session.is_empty() { return Ok(Ident { csrf: ccfg.csrf, @@ -53,7 +53,7 @@ pub fn cookies() -> Result { use self::schema::cookies::dsl::*; trace!("Derive cookies from google chrome..."); - let home = dirs::home_dir().ok_or(Error::NoneError)?; + let home = Config::home_dir(); let p = match std::env::consts::OS { "macos" => home.join("Library/Application Support/Google/Chrome/Default/Cookies"), "linux" => home.join(".config/google-chrome/Default/Cookies"), diff --git a/src/plugins/leetcode.rs b/src/plugins/leetcode.rs index 141c765..d05d32a 100644 --- a/src/plugins/leetcode.rs +++ b/src/plugins/leetcode.rs @@ -1,9 +1,5 @@ use self::req::{Json, Mode, Req}; -use crate::{ - cfg::{self, Config}, - err::Error, - plugins::chrome, -}; +use crate::{cfg::Config, err::Error, plugins::chrome}; use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue}, Client, ClientBuilder, Response, @@ -36,7 +32,7 @@ impl LeetCode { /// New LeetCode client pub fn new() -> Result { - let conf = cfg::locate()?; + let conf = Config::config_content()?; let cookies = chrome::cookies()?; let default_headers = LeetCode::headers( HeaderMap::new(), @@ -44,7 +40,7 @@ impl LeetCode { ("Cookie", cookies.to_string().as_str()), ("x-csrftoken", &cookies.csrf), ("x-requested-with", "XMLHttpRequest"), - ("Origin", &conf.sys.urls["base"]), + ("Origin", &conf.sys.urls.base), ], )?; @@ -68,13 +64,7 @@ impl LeetCode { /// Get category problems pub async fn get_category_problems(self, category: &str) -> Result { trace!("Requesting {} problems...", &category); - let url = &self - .conf - .sys - .urls - .get("problems") - .ok_or(Error::NoneError)? - .replace("$category", category); + let url = &self.conf.sys.urls.problems.replace("$category", category); Req { default_headers: self.default_headers, @@ -91,7 +81,6 @@ impl LeetCode { pub async fn get_question_ids_by_tag(self, slug: &str) -> Result { trace!("Requesting {} ref problems...", &slug); - let url = &self.conf.sys.urls.get("graphql").ok_or(Error::NoneError)?; let mut json: Json = HashMap::new(); json.insert("operationName", "getTopicTag".to_string()); json.insert("variables", r#"{"slug": "$slug"}"#.replace("$slug", slug)); @@ -111,14 +100,12 @@ impl LeetCode { Req { default_headers: self.default_headers, - refer: Some( - (self.conf.sys.urls.get("tag").ok_or(Error::NoneError)?).replace("$slug", slug), - ), + refer: Some(self.conf.sys.urls.tag.replace("$slug", slug)), info: false, json: Some(json), mode: Mode::Post, name: "get_question_ids_by_tag", - url: (*url).to_string(), + url: self.conf.sys.urls.graphql.clone(), } .send(&self.client) .await @@ -126,7 +113,6 @@ impl LeetCode { pub async fn get_user_info(self) -> Result { trace!("Requesting user info..."); - let url = &self.conf.sys.urls.get("graphql").ok_or(Error::NoneError)?; let mut json: Json = HashMap::new(); json.insert("operationName", "a".to_string()); json.insert( @@ -147,7 +133,7 @@ impl LeetCode { json: Some(json), mode: Mode::Post, name: "get_user_info", - url: (*url).to_string(), + url: self.conf.sys.urls.graphql.clone(), } .send(&self.client) .await @@ -156,7 +142,6 @@ impl LeetCode { /// Get daily problem pub async fn get_question_daily(self) -> Result { trace!("Requesting daily problem..."); - let url = &self.conf.sys.urls.get("graphql").ok_or(Error::NoneError)?; let mut json: Json = HashMap::new(); json.insert("operationName", "daily".to_string()); json.insert( @@ -180,7 +165,7 @@ impl LeetCode { json: Some(json), mode: Mode::Post, name: "get_question_daily", - url: (*url).to_string(), + url: self.conf.sys.urls.graphql.clone(), } .send(&self.client) .await @@ -189,13 +174,7 @@ impl LeetCode { /// Get specific problem detail pub async fn get_question_detail(self, slug: &str) -> Result { trace!("Requesting {} detail...", &slug); - let refer = self - .conf - .sys - .urls - .get("problems") - .ok_or(Error::NoneError)? - .replace("$slug", slug); + let refer = self.conf.sys.urls.problems.replace("$slug", slug); let mut json: Json = HashMap::new(); json.insert( "query", @@ -230,7 +209,7 @@ impl LeetCode { json: Some(json), mode: Mode::Post, name: "get_problem_detail", - url: self.conf.sys.urls["graphql"].to_string(), + url: self.conf.sys.urls.graphql.clone(), } .send(&self.client) .await @@ -255,13 +234,7 @@ impl LeetCode { /// Get the result of submission / testing pub async fn verify_result(self, id: String) -> Result { trace!("Verifying result..."); - let url = self - .conf - .sys - .urls - .get("verify") - .ok_or(Error::NoneError)? - .replace("$id", &id); + let url = self.conf.sys.urls.verify.replace("$id", &id); Req { default_headers: self.default_headers, refer: None,