Compare commits
No commits in common. "aa5aaf816ff6ed7a0b2e2ca56bb61ee0322e9af3" and "192f0e757aae18f20c9b5bde10e5a92982e6a42d" have entirely different histories.
aa5aaf816f
...
192f0e757a
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1 @@
|
|||
target
|
||||
.env
|
||||
|
|
756
Cargo.lock
generated
756
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -9,8 +9,6 @@ artem = { version = "=1.1.5", default-features = false }
|
|||
axum = "0.6"
|
||||
color-eyre = "0.6"
|
||||
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"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
119
src/main.rs
119
src/main.rs
|
@ -2,35 +2,18 @@ use std::str::FromStr;
|
|||
|
||||
use axum::{
|
||||
body::BoxBody,
|
||||
extract::State,
|
||||
http::header::{self, HeaderMap},
|
||||
http::header,
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use opentelemetry::{
|
||||
global,
|
||||
trace::{get_active_span, FutureExt, Span, Status, TraceContextExt, Tracer},
|
||||
Context, KeyValue,
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use serde::Deserialize;
|
||||
use tracing::{info, Level};
|
||||
use tracing_subscriber::{filter::Targets, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ServerState {
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
#[tokio::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"))
|
||||
.expect("RUST_LOG should be a valid tracing filter");
|
||||
tracing_subscriber::fmt()
|
||||
|
@ -40,51 +23,18 @@ async fn main() {
|
|||
.with(filter)
|
||||
.init();
|
||||
|
||||
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 app = Router::new().route("/", get(root_get));
|
||||
|
||||
let addr = "0.0.0.0:8080".parse().unwrap();
|
||||
info!("Listening on {addr}");
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.with_graceful_shutdown(quit_signal)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn root_get(headers: HeaderMap, State(state): State<ServerState>) -> Response<BoxBody> {
|
||||
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
|
||||
{
|
||||
async fn root_get() -> Response<BoxBody> {
|
||||
match get_cat_ascii_art().await {
|
||||
Ok(art) => (
|
||||
StatusCode::OK,
|
||||
[(header::CONTENT_TYPE, "text/html; charset=utf-8")],
|
||||
|
@ -92,57 +42,20 @@ async fn root_get_inner(state: ServerState) -> Response<BoxBody> {
|
|||
)
|
||||
.into_response(),
|
||||
Err(e) => {
|
||||
get_active_span(|span| {
|
||||
span.set_status(Status::Error {
|
||||
description: format!("{e}").into(),
|
||||
})
|
||||
});
|
||||
println!("Something went wrong: {e}");
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong").into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
async fn get_cat_ascii_art() -> color_eyre::Result<String> {
|
||||
#[derive(Deserialize)]
|
||||
struct CatImage {
|
||||
url: String,
|
||||
}
|
||||
|
||||
let api_url = "https://api.thecatapi.com/v1/images/search";
|
||||
let client = reqwest::Client::default();
|
||||
|
||||
let image = client
|
||||
.get(api_url)
|
||||
|
@ -153,16 +66,22 @@ async fn get_cat_image_url(client: &reqwest::Client) -> color_eyre::Result<Strin
|
|||
.await?
|
||||
.pop()
|
||||
.ok_or_else(|| color_eyre::eyre::eyre!("The Cat API returned no images"))?;
|
||||
Ok(image.url)
|
||||
}
|
||||
|
||||
async fn download_file(client: &reqwest::Client, url: &str) -> color_eyre::Result<Vec<u8>> {
|
||||
let bytes = client
|
||||
.get(url)
|
||||
let image_bytes = client
|
||||
.get(image.url)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.bytes()
|
||||
.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…
Reference in a new issue