Skip to content

Instantly share code, notes, and snippets.

@malik672
Created February 11, 2024 07:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save malik672/1b8128e39886b1157877cc0aff20180b to your computer and use it in GitHub Desktop.
Save malik672/1b8128e39886b1157877cc0aff20180b to your computer and use it in GitHub Desktop.
rickmorty
// 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 <your.email@example.com>")
.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