Created
February 11, 2024 07:30
-
-
Save malik672/1b8128e39886b1157877cc0aff20180b to your computer and use it in GitHub Desktop.
rickmorty
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Import the crates | |
use clap::{App, Arg, SubCommand}; | |
use reqwest::Client; | |
use std::error::Error; | |
use hyper::service::{make_service_fn, service_fn}; | |
use hyper::{Body, Request, Response, Server}; | |
use std::net::SocketAddr; | |
use std::convert::Infallible; | |
#[tokio::main] | |
async fn main() { | |
// Create a CLI app with clap | |
let mut app = App::new("Rick and Morty CLI") | |
.version("0.1.0") | |
.author("Your Name <[email protected]>") | |
.about("A CLI application to consume the Rick and Morty API") | |
.subcommand( | |
SubCommand::with_name("character") | |
.about("Get information about a character by ID") | |
.arg( | |
Arg::with_name("id") | |
.help("The ID of the character") | |
.required(true) | |
.index(1), | |
), | |
) | |
.subcommand( | |
SubCommand::with_name("episode") | |
.about("Get information about an episode by ID") | |
.arg( | |
Arg::with_name("id") | |
.help("The ID of the episode") | |
.required(true) | |
.index(1), | |
), | |
) | |
.subcommand( | |
SubCommand::with_name("location") | |
.about("Get information about a location by ID") | |
.arg( | |
Arg::with_name("id") | |
.help("The ID of the location") | |
.required(true) | |
.index(1), | |
), | |
) | |
.subcommand( | |
SubCommand::with_name("proxy") | |
.about("Spin up a proxy server to cache the API results") | |
.arg( | |
Arg::with_name("port") | |
.help("The port to listen on") | |
.default_value("8000") | |
.index(1), | |
), | |
) | |
.subcommand(SubCommand::with_name("help").about("Prints help information")); | |
// Parse the command-line arguments | |
let matches = app.clone().get_matches(); | |
// Create a HTTP client with reqwest | |
let client = Client::new(); | |
match matches.subcommand() { | |
Some(("character", sub_matches)) => { | |
// Get the character ID from the argument | |
let id = sub_matches.value_of("id").unwrap(); | |
// Build the URL for the API request | |
let url = format!("https://rickandmortyapi.com/api/character/{}", id); | |
// Make a GET request to the API and get the response | |
let response = client.get(&url).send().await.unwrap(); | |
// Check if the response is successful | |
if response.status().is_success() { | |
// Parse the response body as a JSON value | |
let json_str = response.text().await.unwrap(); | |
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap(); | |
// Print the JSON value | |
println!("{:?}", json); | |
} else { | |
// Print the status code and the error message | |
println!( | |
"Error: {} {}", | |
response.status(), | |
response | |
.text() | |
.await | |
.unwrap_or_else(|_| "Fetching error".to_string()) | |
); | |
} | |
} | |
Some(("episode", sub_matches)) => { | |
// Get the episode ID from the argument | |
let id = sub_matches.value_of("id").unwrap(); | |
// Build the URL for the API request | |
let url = format!("https://rickandmortyapi.com/api/episode/{}", id); | |
// Make a GET request to the API and get the response | |
let response = client.get(&url).send().await.unwrap(); | |
// Check if the response is successful | |
if response.status().is_success() { | |
let json_str = response.text().await.unwrap(); | |
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap(); | |
// Print the JSON value | |
println!("{:?}", json); | |
} else { | |
// Print the status code and the error message | |
println!( | |
"Error: {} {}", | |
response.status(), | |
response | |
.text() | |
.await | |
.unwrap_or_else(|_| "Fetching error".to_string()) | |
); | |
} | |
} | |
Some(("location", sub_matches)) => { | |
// Get the episode ID from the argument | |
let id = sub_matches.value_of("id").unwrap(); | |
// Build the URL for the API request | |
let url = format!("https://rickandmortyapi.com/api/location/{}", id); | |
// Make a GET request to the API and get the response | |
let response = client.get(&url).send().await.unwrap(); | |
// Check if the response is successful | |
if response.status().is_success() { | |
// Parse the response body as a JSON value | |
let json_str = response.text().await.unwrap(); | |
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap(); | |
// Print the JSON value | |
println!("{:?}", json); | |
} else { | |
// Print the status code and the error message | |
println!( | |
"Error: {} {}", | |
response.status(), | |
response | |
.text() | |
.await | |
.unwrap_or_else(|_| "Fetching error".to_string()) | |
); | |
} | |
} | |
Some(("proxy", sub_matches)) => { | |
let port = sub_matches.value_of("port").unwrap_or("8000"); | |
let addr = format!("127.0.0.1:{}", port).parse().unwrap(); | |
println!("Starting proxy server on port {}", port); | |
run_proxy(addr).await.unwrap(); | |
} | |
Some(("help", _)) => { | |
println!("{}", app.render_usage()); | |
} | |
_ => { | |
// Handle the case where no subcommand was provided or an unknown subcommand was used | |
eprintln!("No subcommand provided or an unknown subcommand was used."); | |
} | |
} | |
} | |
async fn run_proxy(addr: SocketAddr) -> Result<(), Box<dyn Error>> { | |
let make_svc = make_service_fn(|_conn| async { | |
Ok::<_, Infallible>(service_fn(|req: Request<Body>| async move { | |
handle_request(req).await | |
})) | |
}); | |
let server = Server::bind(&addr).serve(make_svc); | |
println!("Proxy server listening on http://{}", addr); | |
if let Err(e) = server.await { | |
eprintln!("server error: {}", e); | |
} | |
Ok(()) | |
} | |
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | |
// Forward request to target server | |
let client = reqwest::Client::new(); | |
let target_url = format!("https://rickandmortyapi.com{}", req.uri()); | |
let mut request_builder = client.request(req.method().clone(), &target_url); | |
// Set request headers | |
for (header_name, header_value) in req.headers() { | |
request_builder = request_builder.header(header_name.clone(), header_value.to_str().unwrap()); | |
} | |
let request = request_builder.body(Body::from(req.into_body())).build().expect("can't build request"); | |
let response = client.execute(request).await.expect(""); | |
// Build response to be returned to client | |
let mut builder = Response::builder().status(response.status()); | |
// Set response headers | |
for (header_name, header_value) in response.headers() { | |
builder = builder.header(header_name.clone(), header_value.to_str().unwrap()); | |
} | |
let bytes = response.bytes().await.expect(""); | |
let body = Body::from(bytes); | |
let res = builder.body(body).expect(""); | |
Ok(res) | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[tokio::test] | |
async fn test_character_command() { | |
let id = "1"; | |
let client = Client::new(); | |
let url = format!("https://rickandmortyapi.com/api/character/{}", id); | |
let response = client.get(&url).send().await.unwrap(); | |
assert!(response.status().is_success()); | |
} | |
#[tokio::test] | |
async fn test_episode_command() { | |
let id = "1"; | |
let client = Client::new(); | |
let url = format!("https://rickandmortyapi.com/api/episode/{}", id); | |
let response = client.get(&url).send().await.unwrap(); | |
assert!(response.status().is_success()); | |
} | |
#[tokio::test] | |
async fn test_location_command() { | |
let id = "1"; | |
let client = Client::new(); | |
let url = format!("https://rickandmortyapi.com/api/location/{}", id); | |
let response = client.get(&url).send().await.unwrap(); | |
assert!(response.status().is_success()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment