fix: use mercxry translations api
This commit is contained in:
@@ -74,7 +74,7 @@ Example response:
|
|||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `BIND_ADDR` | `0.0.0.0:5000` | HTTP bind address |
|
| `BIND_ADDR` | `0.0.0.0:5000` | HTTP bind address |
|
||||||
| `POKEAPI_BASE_URL` | `https://pokeapi.co/api/v2` | PokéAPI base URL |
|
| `POKEAPI_BASE_URL` | `https://pokeapi.co/api/v2` | PokéAPI base URL |
|
||||||
| `FUN_TRANSLATIONS_BASE_URL` | `https://funtranslations.mercxry.me` | FunTranslations base URL |
|
| `FUN_TRANSLATIONS_BASE_URL` | `https://api.funtranslations.mercxry.me/v1` | FunTranslations base URL |
|
||||||
| `REQUEST_TIMEOUT_SECONDS` | `5` | Upstream HTTP timeout |
|
| `REQUEST_TIMEOUT_SECONDS` | `5` | Upstream HTTP timeout |
|
||||||
| `RATE_LIMIT_PER_SECOND` | `20` | Global token refill rate |
|
| `RATE_LIMIT_PER_SECOND` | `20` | Global token refill rate |
|
||||||
| `RATE_LIMIT_BURST` | `40` | Global burst capacity |
|
| `RATE_LIMIT_BURST` | `40` | Global burst capacity |
|
||||||
|
|||||||
@@ -150,8 +150,8 @@ mod tests {
|
|||||||
let translations = MockServer::start().await;
|
let translations = MockServer::start().await;
|
||||||
mount_species(&pokeapi, "mewtwo", true, "rare", "Created by science.").await;
|
mount_species(&pokeapi, "mewtwo", true, "rare", "Created by science.").await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/yoda.json"))
|
.and(matchers::path("/translate/yoda"))
|
||||||
.and(matchers::body_string_contains("Created+by+science"))
|
.and(matchers::body_string_contains("Created by science"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"contents": { "translated": "Created by science, it was." }
|
"contents": { "translated": "Created by science, it was." }
|
||||||
})))
|
})))
|
||||||
@@ -174,7 +174,7 @@ mod tests {
|
|||||||
let translations = MockServer::start().await;
|
let translations = MockServer::start().await;
|
||||||
mount_species(&pokeapi, "pikachu", false, "forest", "Electric cheeks.").await;
|
mount_species(&pokeapi, "pikachu", false, "forest", "Electric cheeks.").await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/shakespeare.json"))
|
.and(matchers::path("/translate/shakespeare"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"contents": { "translated": "Electric cheeks, good sir." }
|
"contents": { "translated": "Electric cheeks, good sir." }
|
||||||
})))
|
})))
|
||||||
@@ -197,7 +197,7 @@ mod tests {
|
|||||||
let translations = MockServer::start().await;
|
let translations = MockServer::start().await;
|
||||||
mount_species(&pokeapi, "zubat", false, "cave", "It has no eyes.").await;
|
mount_species(&pokeapi, "zubat", false, "cave", "It has no eyes.").await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/yoda.json"))
|
.and(matchers::path("/translate/yoda"))
|
||||||
.respond_with(ResponseTemplate::new(500))
|
.respond_with(ResponseTemplate::new(500))
|
||||||
.expect(1)
|
.expect(1)
|
||||||
.mount(&translations)
|
.mount(&translations)
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ impl TranslationClient {
|
|||||||
|
|
||||||
#[instrument(skip(self, text), fields(translation.kind = ?kind))]
|
#[instrument(skip(self, text), fields(translation.kind = ?kind))]
|
||||||
pub async fn translate(&self, kind: TranslationKind, text: &str) -> Result<String, AppError> {
|
pub async fn translate(&self, kind: TranslationKind, text: &str) -> Result<String, AppError> {
|
||||||
let url = format!("{}/translate/{}.json", self.base_url, endpoint_for(kind));
|
let url = format!("{}/translate/{}", self.base_url, endpoint_for(kind));
|
||||||
let response = self
|
let response = self
|
||||||
.http
|
.http
|
||||||
.post(url)
|
.post(url)
|
||||||
.form(&[("text", text)])
|
.json(&TranslationRequest { text })
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(|error| map_reqwest_error(&error))?;
|
.map_err(|error| map_reqwest_error(&error))?;
|
||||||
@@ -51,6 +51,11 @@ impl TranslationClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct TranslationRequest<'a> {
|
||||||
|
text: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct TranslationResponse {
|
struct TranslationResponse {
|
||||||
contents: TranslationContents,
|
contents: TranslationContents,
|
||||||
@@ -97,8 +102,8 @@ mod tests {
|
|||||||
async fn posts_text_to_the_requested_translation_endpoint() {
|
async fn posts_text_to_the_requested_translation_endpoint() {
|
||||||
let server = MockServer::start().await;
|
let server = MockServer::start().await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/yoda.json"))
|
.and(matchers::path("/translate/yoda"))
|
||||||
.and(matchers::body_string_contains("text=Created"))
|
.and(matchers::body_string_contains("Created"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"contents": { "translated": "Created, it was." }
|
"contents": { "translated": "Created, it was." }
|
||||||
})))
|
})))
|
||||||
@@ -119,7 +124,7 @@ mod tests {
|
|||||||
async fn rejects_empty_translations_so_callers_can_fallback() {
|
async fn rejects_empty_translations_so_callers_can_fallback() {
|
||||||
let server = MockServer::start().await;
|
let server = MockServer::start().await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/yoda.json"))
|
.and(matchers::path("/translate/yoda"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"contents": { "translated": " " }
|
"contents": { "translated": " " }
|
||||||
})))
|
})))
|
||||||
@@ -139,7 +144,7 @@ mod tests {
|
|||||||
async fn maps_translation_http_failures_to_upstream_errors() {
|
async fn maps_translation_http_failures_to_upstream_errors() {
|
||||||
let server = MockServer::start().await;
|
let server = MockServer::start().await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/shakespeare.json"))
|
.and(matchers::path("/translate/shakespeare"))
|
||||||
.respond_with(ResponseTemplate::new(429))
|
.respond_with(ResponseTemplate::new(429))
|
||||||
.mount(&server)
|
.mount(&server)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ impl AppConfig {
|
|||||||
Self {
|
Self {
|
||||||
bind_addr: default_bind_addr(),
|
bind_addr: default_bind_addr(),
|
||||||
pokeapi_base_url: "https://pokeapi.co/api/v2".to_owned(),
|
pokeapi_base_url: "https://pokeapi.co/api/v2".to_owned(),
|
||||||
translations_base_url: "https://funtranslations.mercxry.me".to_owned(),
|
translations_base_url: "https://api.funtranslations.mercxry.me/v1".to_owned(),
|
||||||
request_timeout: Duration::from_secs(5),
|
request_timeout: Duration::from_secs(5),
|
||||||
rate_limit_per_second: 20.0,
|
rate_limit_per_second: 20.0,
|
||||||
rate_limit_burst: 40,
|
rate_limit_burst: 40,
|
||||||
|
|||||||
14
tests/app.rs
14
tests/app.rs
@@ -79,8 +79,8 @@ async fn translated_endpoint_returns_translated_description() {
|
|||||||
let translations = MockServer::start().await;
|
let translations = MockServer::start().await;
|
||||||
mount_species(&pokeapi, "pikachu", false, "forest", "Electric cheeks.").await;
|
mount_species(&pokeapi, "pikachu", false, "forest", "Electric cheeks.").await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/shakespeare.json"))
|
.and(matchers::path("/translate/shakespeare"))
|
||||||
.and(matchers::body_string_contains("Electric+cheeks"))
|
.and(matchers::body_string_contains("Electric cheeks"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"contents": { "translated": "Electric cheeks, prithee." }
|
"contents": { "translated": "Electric cheeks, prithee." }
|
||||||
})))
|
})))
|
||||||
@@ -112,8 +112,8 @@ async fn translated_endpoint_uses_yoda_for_legendary_pokemon() {
|
|||||||
let translations = MockServer::start().await;
|
let translations = MockServer::start().await;
|
||||||
mount_species(&pokeapi, "mewtwo", true, "rare", "Created by science.").await;
|
mount_species(&pokeapi, "mewtwo", true, "rare", "Created by science.").await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/yoda.json"))
|
.and(matchers::path("/translate/yoda"))
|
||||||
.and(matchers::body_string_contains("Created+by+science"))
|
.and(matchers::body_string_contains("Created by science"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"contents": { "translated": "Created by science, it was." }
|
"contents": { "translated": "Created by science, it was." }
|
||||||
})))
|
})))
|
||||||
@@ -121,7 +121,7 @@ async fn translated_endpoint_uses_yoda_for_legendary_pokemon() {
|
|||||||
.mount(&translations)
|
.mount(&translations)
|
||||||
.await;
|
.await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/shakespeare.json"))
|
.and(matchers::path("/translate/shakespeare"))
|
||||||
.respond_with(ResponseTemplate::new(200))
|
.respond_with(ResponseTemplate::new(200))
|
||||||
.expect(0)
|
.expect(0)
|
||||||
.mount(&translations)
|
.mount(&translations)
|
||||||
@@ -151,7 +151,7 @@ async fn translated_endpoint_falls_back_to_original_description_when_translation
|
|||||||
let translations = MockServer::start().await;
|
let translations = MockServer::start().await;
|
||||||
mount_species(&pokeapi, "zubat", false, "cave", "It has no eyes.").await;
|
mount_species(&pokeapi, "zubat", false, "cave", "It has no eyes.").await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/yoda.json"))
|
.and(matchers::path("/translate/yoda"))
|
||||||
.respond_with(ResponseTemplate::new(500))
|
.respond_with(ResponseTemplate::new(500))
|
||||||
.mount(&translations)
|
.mount(&translations)
|
||||||
.await;
|
.await;
|
||||||
@@ -180,7 +180,7 @@ async fn translated_endpoint_falls_back_when_translation_is_empty() {
|
|||||||
let translations = MockServer::start().await;
|
let translations = MockServer::start().await;
|
||||||
mount_species(&pokeapi, "zubat", false, "cave", "It has no eyes.").await;
|
mount_species(&pokeapi, "zubat", false, "cave", "It has no eyes.").await;
|
||||||
Mock::given(matchers::method("POST"))
|
Mock::given(matchers::method("POST"))
|
||||||
.and(matchers::path("/translate/yoda.json"))
|
.and(matchers::path("/translate/yoda"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"contents": { "translated": " " }
|
"contents": { "translated": " " }
|
||||||
})))
|
})))
|
||||||
|
|||||||
Reference in New Issue
Block a user