use std::path::Path;
use reqwest::multipart;
use serde_json::Value;
use thiserror::Error;
const API_URL: &str = "https://www.pelock.com/api/aztec-decoder/v1";
#[derive(Debug, Error)]
pub enum AZTecError {
#[error("brak klucza API")]
EmptyApiKey,
#[error("błąd odczytu pliku: {0}")]
FileRead(#[from] std::io::Error),
#[error("błąd żądania HTTP: {0}")]
Request(#[from] reqwest::Error),
#[error("nieprawidłowa odpowiedź JSON: {0}")]
InvalidJson(#[from] serde_json::Error),
}
pub struct AZTecDecoder {
api_key: String,
client: reqwest::Client,
}
impl AZTecDecoder {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
client: reqwest::Client::new(),
}
}
pub async fn decode_text(&self, text: &str) -> Result<Value, AZTecError> {
let form = multipart::Form::new()
.text("key", self.api_key.clone())
.text("command", "decode-text")
.text("text", text.to_owned());
self.post_request(form).await
}
pub async fn decode_text_from_file(
&self,
path: impl AsRef<Path>,
) -> Result<Value, AZTecError> {
let data = tokio::fs::read_to_string(path).await?;
self.decode_text(&data).await
}
pub async fn decode_image_from_file(
&self,
path: impl AsRef<Path>,
) -> Result<Value, AZTecError> {
if self.api_key.is_empty() {
return Err(AZTecError::EmptyApiKey);
}
let path = path.as_ref();
let file_bytes = tokio::fs::read(path).await?;
let file_name = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_default();
let file_part = multipart::Part::bytes(file_bytes).file_name(file_name);
let form = multipart::Form::new()
.text("key", self.api_key.clone())
.text("command", "decode-image")
.part("image", file_part);
self.post_request(form).await
}
async fn post_request(&self, form: multipart::Form) -> Result<Value, AZTecError> {
if self.api_key.is_empty() {
return Err(AZTecError::EmptyApiKey);
}
let response = self
.client
.post(API_URL)
.multipart(form)
.send()
.await?;
let text = response.text().await?;
let json: Value = serde_json::from_str(&text)?;
Ok(json)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_decoder_stores_api_key() {
let decoder = AZTecDecoder::new("test-key");
assert_eq!(decoder.api_key, "test-key");
}
#[tokio::test]
async fn empty_api_key_returns_error() {
let decoder = AZTecDecoder::new("");
let result = decoder.decode_text("test").await;
assert!(matches!(result, Err(AZTecError::EmptyApiKey)));
}
#[tokio::test]
async fn missing_file_returns_error() {
let decoder = AZTecDecoder::new("test-key");
let result = decoder
.decode_text_from_file("nieistniejacy-plik.txt")
.await;
assert!(matches!(result, Err(AZTecError::FileRead(_))));
}
#[tokio::test]
async fn missing_image_returns_error() {
let decoder = AZTecDecoder::new("test-key");
let result = decoder
.decode_image_from_file("nieistniejacy-obrazek.png")
.await;
assert!(matches!(result, Err(AZTecError::FileRead(_))));
}
}