diff --git a/Cargo.lock b/Cargo.lock index 0964433..9d48bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -2856,6 +2862,7 @@ dependencies = [ name = "yoda-web" version = "0.1.0" dependencies = [ + "anyhow", "axum", "chrono", "config", diff --git a/Cargo.toml b/Cargo.toml index ed4e5c9..5b4743d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,8 @@ reqwest = { version = "0.12", default-features = false, features = [ "json", "rustls-tls", ] } +serde_json = "1" +anyhow = "1" [dev-dependencies] diff --git a/src/domain/mod.rs b/src/domain/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/domain/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib.rs b/src/lib.rs index f24afc4..439ffcf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod config; -pub mod domain; pub mod routes; +pub mod speak; +pub mod state; pub mod telemetry; diff --git a/src/main.rs b/src/main.rs index ccc770d..41b1a22 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,23 @@ +use std::sync::Arc; + +use tokio::net::TcpListener; use yoda_web::{ config::get_config, routes::route, + state::AppState, telemetry::{get_subscriber, init_subscriber}, }; -use sqlx::postgres::PgPoolOptions; -use tokio::net::TcpListener; #[tokio::main] async fn main() -> Result<(), std::io::Error> { let subscriber = get_subscriber("yoda-web", "info", std::io::stdout); init_subscriber(subscriber); let config = get_config().expect("Failed to read configuation."); - let pool = PgPoolOptions::new().connect_lazy_with(config.database.with_db()); + let state = Arc::new(AppState::default()); let addr = format!("{}:{}", config.application.host, config.application.port); let listener = TcpListener::bind(addr) .await .expect("Failed to bind port 8000."); - axum::serve(listener, route(pool)).await + axum::serve(listener, route(state)).await } diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 65e6768..44344b6 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,13 +1,13 @@ mod health_check; -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use axum::{ body::Bytes, extract::MatchedPath, http::{HeaderMap, Request}, response::Response, - routing::get, + routing::{get, post}, Router, }; @@ -17,10 +17,13 @@ use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer}; use tracing::{info_span, Span}; use uuid::Uuid; -pub fn route(pool: PgPool) -> Router { +use crate::{speak::yoda_speak, state::AppState}; + +pub fn route(state: Arc) -> Router { Router::new() .route("/health_check", get(health_check)) - .with_state(pool) + .route("/yoda", post(yoda_speak)) + .with_state(state) .layer( TraceLayer::new_for_http() .make_span_with(|request: &Request<_>| { diff --git a/src/speak.rs b/src/speak.rs new file mode 100644 index 0000000..058db95 --- /dev/null +++ b/src/speak.rs @@ -0,0 +1,66 @@ +use std::sync::Arc; + +use anyhow::{Context, Result}; +use serde_json::{json, Value}; + +use axum::{extract::State, response::IntoResponse, Json}; +use reqwest::{Client, StatusCode}; +use serde::{Deserialize, Serialize}; +use tracing_log::log::error; + +use crate::state::AppState; + +#[derive(Debug, Deserialize)] +pub struct InputText { + pub text: String, +} + +#[derive(Debug, Serialize)] +pub struct OutputText { + pub yoda_text: String, +} + +pub async fn yoda_speak( + State(state): State>, + Json(input): Json, +) -> impl IntoResponse { + match convert_to_yoda_speak(&state.client, &input.text).await { + Ok(yoda_text) => { + let output = OutputText { yoda_text }; + (StatusCode::OK, Json(output)).into_response() + } + Err(e) => { + error!("Error converting to Yoda speak: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({"error":"Failed to convert text"})), + ) + .into_response() + } + } +} + +async fn convert_to_yoda_speak(client: &Client, text: &str) -> Result { + let url = "https://api.funtranslations.com/translate/yoda.json"; + let params = [("text", text)]; + let response = client + .post(url) + .form(¶ms) + .send() + .await + .context("Failed to send request to Yoda translation API")?; + let json: Value = response + .json() + .await + .context("Failed to parse JSON response")?; + + let translated_text = json + .get("contents") + .context("Missing 'contents' key in JSON response")? + .get("translated") + .context("Missing 'translated' key in 'contents' object")? + .as_str() + .context("Failed to extract translated text from JSON")? + .to_owned(); + Ok(translated_text) +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..f683c22 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,20 @@ +use reqwest::Client; + +#[derive(Clone)] +pub struct AppState { + pub client: Client, +} + +impl AppState { + pub fn new() -> Self { + Self::default() + } +} + +impl Default for AppState { + fn default() -> Self { + Self { + client: Client::new(), + } + } +}