feat: add translator

This commit is contained in:
Kristofers Solo 2024-08-20 22:07:48 +03:00
parent 6373738bda
commit e85cec1a7e
8 changed files with 110 additions and 10 deletions

7
Cargo.lock generated
View File

@ -60,6 +60,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anyhow"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
[[package]] [[package]]
name = "assert-json-diff" name = "assert-json-diff"
version = "2.0.2" version = "2.0.2"
@ -2856,6 +2862,7 @@ dependencies = [
name = "yoda-web" name = "yoda-web"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"axum", "axum",
"chrono", "chrono",
"config", "config",

View File

@ -43,6 +43,8 @@ reqwest = { version = "0.12", default-features = false, features = [
"json", "json",
"rustls-tls", "rustls-tls",
] } ] }
serde_json = "1"
anyhow = "1"
[dev-dependencies] [dev-dependencies]

View File

@ -1 +0,0 @@

View File

@ -1,4 +1,5 @@
pub mod config; pub mod config;
pub mod domain;
pub mod routes; pub mod routes;
pub mod speak;
pub mod state;
pub mod telemetry; pub mod telemetry;

View File

@ -1,21 +1,23 @@
use std::sync::Arc;
use tokio::net::TcpListener;
use yoda_web::{ use yoda_web::{
config::get_config, config::get_config,
routes::route, routes::route,
state::AppState,
telemetry::{get_subscriber, init_subscriber}, telemetry::{get_subscriber, init_subscriber},
}; };
use sqlx::postgres::PgPoolOptions;
use tokio::net::TcpListener;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), std::io::Error> { async fn main() -> Result<(), std::io::Error> {
let subscriber = get_subscriber("yoda-web", "info", std::io::stdout); let subscriber = get_subscriber("yoda-web", "info", std::io::stdout);
init_subscriber(subscriber); init_subscriber(subscriber);
let config = get_config().expect("Failed to read configuation."); 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 addr = format!("{}:{}", config.application.host, config.application.port);
let listener = TcpListener::bind(addr) let listener = TcpListener::bind(addr)
.await .await
.expect("Failed to bind port 8000."); .expect("Failed to bind port 8000.");
axum::serve(listener, route(pool)).await axum::serve(listener, route(state)).await
} }

View File

@ -1,13 +1,13 @@
mod health_check; mod health_check;
use std::time::Duration; use std::{sync::Arc, time::Duration};
use axum::{ use axum::{
body::Bytes, body::Bytes,
extract::MatchedPath, extract::MatchedPath,
http::{HeaderMap, Request}, http::{HeaderMap, Request},
response::Response, response::Response,
routing::get, routing::{get, post},
Router, Router,
}; };
@ -17,10 +17,13 @@ use tower_http::{classify::ServerErrorsFailureClass, trace::TraceLayer};
use tracing::{info_span, Span}; use tracing::{info_span, Span};
use uuid::Uuid; use uuid::Uuid;
pub fn route(pool: PgPool) -> Router { use crate::{speak::yoda_speak, state::AppState};
pub fn route(state: Arc<AppState>) -> Router {
Router::new() Router::new()
.route("/health_check", get(health_check)) .route("/health_check", get(health_check))
.with_state(pool) .route("/yoda", post(yoda_speak))
.with_state(state)
.layer( .layer(
TraceLayer::new_for_http() TraceLayer::new_for_http()
.make_span_with(|request: &Request<_>| { .make_span_with(|request: &Request<_>| {

66
src/speak.rs Normal file
View File

@ -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<Arc<AppState>>,
Json(input): Json<InputText>,
) -> 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<String> {
let url = "https://api.funtranslations.com/translate/yoda.json";
let params = [("text", text)];
let response = client
.post(url)
.form(&params)
.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)
}

20
src/state.rs Normal file
View File

@ -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(),
}
}
}