mod configs; mod error; mod handlers; mod models; mod repo; use std::{fs, sync::Arc}; use axum::{routing::get, Router}; use configs::Config; use error::{AppError, AppResult}; use models::AppState; use reqwest::Url; use tokio::{net::TcpListener, sync::RwLock}; #[macro_use] extern crate log; /// Parse urls and return 2 lists: valid urls and errors fn parse_urls(url_list: &[String]) -> (Vec, Vec) { let (valid, invalid): (Vec<_>, Vec<_>) = url_list .into_iter() .map(|url| Url::parse(&url).map_err(|e| AppError::InvalidUrl(url.to_string(), e))) .partition(Result::is_ok); ( valid.into_iter().map(Result::unwrap).collect(), invalid .into_iter() .map(|e| e.unwrap_err().to_string()) .collect(), ) } /// Extract raw urls from different sources: /// - from args list /// - from file fn parse_urls_from_args( args: impl IntoIterator, ) -> AppResult<(Vec, Vec)> { let mut args_iter = args.into_iter(); match args_iter.nth(1) { Some(action) => match action.as_str() { "-f" => { let Some(source) = args_iter.next() else { return Err(AppError::UsageError); }; println!("Reading file '{source}'"); let raw_urls = fs::read_to_string(source)? .split('\n') .map(|s| s.to_owned()) .collect::>(); Ok(parse_urls(&raw_urls)) } "-u" => { let urls_list = args_iter.collect::>(); Ok(parse_urls(&urls_list)) } _ => return Err(AppError::UsageError), }, None => return Err(AppError::UsageError), } } async fn process_urls(config: &Config, urls: &[Url]) {} async fn run() -> AppResult<()> { dotenv::dotenv().ok(); let cfg = configs::get_configs()?; info!("{cfg:?}"); // let (valid_urls, errors) = parse_urls_from_args(args)?; let state = Arc::new(RwLock::new(AppState::new(cfg.url_handlers_pool_size))); let app = Router::new() .route( "/urls", get(handlers::get_urls) .post(handlers::upload_urls) .delete(handlers::delete_urls), ) .with_state(state); let listener = TcpListener::bind("0.0.0.0:8000").await?; axum::serve(listener, app).await.map_err(AppError::from) } #[tokio::main] async fn main() { env_logger::init(); if let Err(e) = run().await { println!("{e}"); } }