Compare commits
	
		
			4 commits
		
	
	
		
			192f0e757a
			...
			aa5aaf816f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| aa5aaf816f | |||
| 460c002145 | |||
| bb32083d2c | |||
| c813356eaa | 
					 5 changed files with 833 additions and 48 deletions
				
			
		
							
								
								
									
										3
									
								
								.envrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.envrc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | watch_file flake.nix flake.lock | ||||||
|  | dotenv_if_exists | ||||||
|  | use flake | ||||||
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -1 +1,2 @@ | ||||||
| target | target | ||||||
|  | .env | ||||||
|  |  | ||||||
							
								
								
									
										756
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										756
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -9,6 +9,8 @@ artem = { version = "=1.1.5", default-features = false } | ||||||
| axum = "0.6" | axum = "0.6" | ||||||
| color-eyre = "0.6" | color-eyre = "0.6" | ||||||
| image = "0.24" | image = "0.24" | ||||||
|  | opentelemetry = { version = "0.18", features = ["rt-tokio"] } | ||||||
|  | opentelemetry-honeycomb = { git = "https://github.com/fasterthanlime/opentelemetry-honeycomb-rs", branch = "simplified", version = "0.1.0" } | ||||||
| pretty-hex = "0.3" | pretty-hex = "0.3" | ||||||
| reqwest = { version = "0.11", features = ["json"] } | reqwest = { version = "0.11", features = ["json"] } | ||||||
| serde = { version = "1", features = ["derive"] } | serde = { version = "1", features = ["derive"] } | ||||||
|  |  | ||||||
							
								
								
									
										119
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										119
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -2,18 +2,35 @@ use std::str::FromStr; | ||||||
| 
 | 
 | ||||||
| use axum::{ | use axum::{ | ||||||
|     body::BoxBody, |     body::BoxBody, | ||||||
|     http::header, |     extract::State, | ||||||
|  |     http::header::{self, HeaderMap}, | ||||||
|     response::{IntoResponse, Response}, |     response::{IntoResponse, Response}, | ||||||
|     routing::get, |     routing::get, | ||||||
|     Router, |     Router, | ||||||
| }; | }; | ||||||
|  | use opentelemetry::{ | ||||||
|  |     global, | ||||||
|  |     trace::{get_active_span, FutureExt, Span, Status, TraceContextExt, Tracer}, | ||||||
|  |     Context, KeyValue, | ||||||
|  | }; | ||||||
| use reqwest::StatusCode; | use reqwest::StatusCode; | ||||||
| use serde::Deserialize; | use serde::Deserialize; | ||||||
| use tracing::{info, Level}; | use tracing::{info, Level}; | ||||||
| use tracing_subscriber::{filter::Targets, layer::SubscriberExt, util::SubscriberInitExt}; | use tracing_subscriber::{filter::Targets, layer::SubscriberExt, util::SubscriberInitExt}; | ||||||
| 
 | 
 | ||||||
|  | #[derive(Clone)] | ||||||
|  | struct ServerState { | ||||||
|  |     client: reqwest::Client, | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|  |     let (_honeyguard, _tracer) = opentelemetry_honeycomb::new_pipeline( | ||||||
|  |         std::env::var("HONEYCOMB_API_KEY").expect("$HONEYCOMB_API_KEY should be set"), | ||||||
|  |         "catscii".into(), | ||||||
|  |     ) | ||||||
|  |     .install() | ||||||
|  |     .unwrap(); | ||||||
|     let filter = Targets::from_str(std::env::var("RUST_LOG").as_deref().unwrap_or("info")) |     let filter = Targets::from_str(std::env::var("RUST_LOG").as_deref().unwrap_or("info")) | ||||||
|         .expect("RUST_LOG should be a valid tracing filter"); |         .expect("RUST_LOG should be a valid tracing filter"); | ||||||
|     tracing_subscriber::fmt() |     tracing_subscriber::fmt() | ||||||
|  | @ -23,18 +40,51 @@ async fn main() { | ||||||
|         .with(filter) |         .with(filter) | ||||||
|         .init(); |         .init(); | ||||||
| 
 | 
 | ||||||
|     let app = Router::new().route("/", get(root_get)); |     let state = ServerState { | ||||||
|  |         client: Default::default(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let app = Router::new().route("/", get(root_get)).with_state(state); | ||||||
|  | 
 | ||||||
|  |     let quit_signal = async { | ||||||
|  |         _ = tokio::signal::ctrl_c().await; | ||||||
|  |         eprintln!("Initializing graceful shutdown"); | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     let addr = "0.0.0.0:8080".parse().unwrap(); |     let addr = "0.0.0.0:8080".parse().unwrap(); | ||||||
|     info!("Listening on {addr}"); |     info!("Listening on {addr}"); | ||||||
|     axum::Server::bind(&addr) |     axum::Server::bind(&addr) | ||||||
|         .serve(app.into_make_service()) |         .serve(app.into_make_service()) | ||||||
|  |         .with_graceful_shutdown(quit_signal) | ||||||
|         .await |         .await | ||||||
|         .unwrap(); |         .unwrap(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async fn root_get() -> Response<BoxBody> { | async fn root_get(headers: HeaderMap, State(state): State<ServerState>) -> Response<BoxBody> { | ||||||
|     match get_cat_ascii_art().await { |     let tracer = global::tracer(""); | ||||||
|  |     let mut span = tracer.start("root_get"); | ||||||
|  |     span.set_attribute(KeyValue::new( | ||||||
|  |         "user_agent", | ||||||
|  |         headers | ||||||
|  |             .get(header::USER_AGENT) | ||||||
|  |             .map(|h| h.to_str().unwrap_or_default().to_owned()) | ||||||
|  |             .unwrap_or_default(), | ||||||
|  |     )); | ||||||
|  | 
 | ||||||
|  |     root_get_inner(state) | ||||||
|  |         .with_context(Context::current_with_span(span)) | ||||||
|  |         .await | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async fn root_get_inner(state: ServerState) -> Response<BoxBody> { | ||||||
|  |     let tracer = global::tracer(""); | ||||||
|  | 
 | ||||||
|  |     match get_cat_ascii_art(&state.client) | ||||||
|  |         .with_context(Context::current_with_span( | ||||||
|  |                 tracer.start("get_cat_arscii_art"), | ||||||
|  |         )) | ||||||
|  |         .await | ||||||
|  |         { | ||||||
|         Ok(art) => ( |         Ok(art) => ( | ||||||
|             StatusCode::OK, |             StatusCode::OK, | ||||||
|             [(header::CONTENT_TYPE, "text/html; charset=utf-8")], |             [(header::CONTENT_TYPE, "text/html; charset=utf-8")], | ||||||
|  | @ -42,20 +92,57 @@ async fn root_get() -> Response<BoxBody> { | ||||||
|         ) |         ) | ||||||
|             .into_response(), |             .into_response(), | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             println!("Something went wrong: {e}"); |             get_active_span(|span| { | ||||||
|  |                 span.set_status(Status::Error { | ||||||
|  |                     description: format!("{e}").into(), | ||||||
|  |                 }) | ||||||
|  |             }); | ||||||
|             (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response() |             (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async fn get_cat_ascii_art() -> color_eyre::Result<String> { | async fn get_cat_ascii_art(client: &reqwest::Client) -> color_eyre::Result<String> { | ||||||
|  |     let tracer = global::tracer(""); | ||||||
|  | 
 | ||||||
|  |     let image_url = get_cat_image_url(client) | ||||||
|  |         .with_context(Context::current_with_span( | ||||||
|  |             tracer.start("get_cat_image_url"), | ||||||
|  |         )) | ||||||
|  |         .await?; | ||||||
|  | 
 | ||||||
|  |     let image_bytes = download_file(client, &image_url) | ||||||
|  |         .with_context(Context::current_with_span(tracer.start("download_file"))) | ||||||
|  |         .await?; | ||||||
|  | 
 | ||||||
|  |     let image = tracer.in_span("image::load_from_memory", |cx| { | ||||||
|  |         let img = image::load_from_memory(&image_bytes)?; | ||||||
|  |         cx.span() | ||||||
|  |             .set_attribute(KeyValue::new("width", img.width() as i64)); | ||||||
|  |         cx.span() | ||||||
|  |             .set_attribute(KeyValue::new("height", img.height() as i64)); | ||||||
|  |         Ok::<_, color_eyre::eyre::Report>(img) | ||||||
|  |     })?; | ||||||
|  | 
 | ||||||
|  |     let ascii_art = tracer.in_span("artem::convert", |_cx| { | ||||||
|  |         artem::convert( | ||||||
|  |             image, | ||||||
|  |             artem::options::OptionBuilder::new() | ||||||
|  |                 .target(artem::options::TargetType::HtmlFile(true, true)) | ||||||
|  |                 .build(), | ||||||
|  |         ) | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     Ok(ascii_art) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async fn get_cat_image_url(client: &reqwest::Client) -> color_eyre::Result<String> { | ||||||
|     #[derive(Deserialize)] |     #[derive(Deserialize)] | ||||||
|     struct CatImage { |     struct CatImage { | ||||||
|         url: String, |         url: String, | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let api_url = "https://api.thecatapi.com/v1/images/search"; |     let api_url = "https://api.thecatapi.com/v1/images/search"; | ||||||
|     let client = reqwest::Client::default(); |  | ||||||
| 
 | 
 | ||||||
|     let image = client |     let image = client | ||||||
|         .get(api_url) |         .get(api_url) | ||||||
|  | @ -66,22 +153,16 @@ async fn get_cat_ascii_art() -> color_eyre::Result<String> { | ||||||
|         .await? |         .await? | ||||||
|         .pop() |         .pop() | ||||||
|         .ok_or_else(|| color_eyre::eyre::eyre!("The Cat API returned no images"))?; |         .ok_or_else(|| color_eyre::eyre::eyre!("The Cat API returned no images"))?; | ||||||
|  |     Ok(image.url) | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     let image_bytes = client | async fn download_file(client: &reqwest::Client, url: &str) -> color_eyre::Result<Vec<u8>> { | ||||||
|         .get(image.url) |     let bytes = client | ||||||
|  |         .get(url) | ||||||
|         .send() |         .send() | ||||||
|         .await? |         .await? | ||||||
|         .error_for_status()? |         .error_for_status()? | ||||||
|         .bytes() |         .bytes() | ||||||
|         .await?; |         .await?; | ||||||
| 
 |     Ok(bytes.to_vec()) | ||||||
|     let image = image::load_from_memory(&image_bytes)?; |  | ||||||
|     let ascii_art = artem::convert( |  | ||||||
|         image, |  | ||||||
|         artem::options::OptionBuilder::new() |  | ||||||
|             .target(artem::options::TargetType::HtmlFile(true, true)) |  | ||||||
|             .build(), |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     Ok(ascii_art) |  | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue