use std::collections::HashMap;
use reqwest::Client;
use serde_json::{json, Value};
use crate::error::{RadioCodeCalculatorError, RadioErrors};
use crate::model::RadioModel;
#[derive(Debug, Clone)]
pub struct InfoResult {
pub value: Value,
pub radio_model: RadioModel,
}
#[derive(Debug, Clone)]
pub struct ListResult {
pub value: Value,
pub radio_models: Vec<RadioModel>,
}
pub trait AsRadioModelName {
fn radio_model_name(&self) -> &str;
}
impl AsRadioModelName for RadioModel {
fn radio_model_name(&self) -> &str {
&self.name
}
}
impl AsRadioModelName for &'_ RadioModel {
fn radio_model_name(&self) -> &str {
&self.name
}
}
impl AsRadioModelName for str {
fn radio_model_name(&self) -> &str {
self
}
}
impl AsRadioModelName for String {
fn radio_model_name(&self) -> &str {
self.as_str()
}
}
pub struct RadioCodeCalculator {
pub api_url: String,
_api_key: Option<String>,
client: Client,
}
impl RadioCodeCalculator {
pub const DEFAULT_API_URL: &'static str = "https://www.pelock.com/api/radio-code-calculator/v1";
pub fn new(api_key: Option<String>) -> Self {
Self::with_client(api_key, Client::new())
}
pub fn with_client(api_key: Option<String>, client: Client) -> Self {
Self {
api_url: Self::DEFAULT_API_URL.to_string(),
_api_key: api_key,
client,
}
}
pub async fn login(&self) -> Result<Value, RadioCodeCalculatorError> {
let mut params = HashMap::new();
params.insert("command".to_string(), "login".to_string());
self.post_request(params).await
}
pub async fn calc<R: AsRadioModelName + ?Sized>(
&self,
radio_model: &R,
radio_serial_number: &str,
radio_extra_data: &str,
) -> Result<Value, RadioCodeCalculatorError> {
let mut params = HashMap::new();
params.insert("command".to_string(), "calc".to_string());
params.insert(
"radio_model".to_string(),
radio_model.radio_model_name().to_string(),
);
params.insert("serial".to_string(), radio_serial_number.to_string());
params.insert("extra".to_string(), radio_extra_data.to_string());
self.post_request(params).await
}
pub async fn info<R: AsRadioModelName + ?Sized>(
&self,
radio_model: &R,
) -> Result<InfoResult, RadioCodeCalculatorError> {
let mut params = HashMap::new();
params.insert("command".to_string(), "info".to_string());
let name = radio_model.radio_model_name().to_string();
params.insert("radio_model".to_string(), name.clone());
let mut value = self.post_request(params).await?;
let model = radio_model_from_response(&name, &value).ok_or_else(|| {
RadioCodeCalculatorError::Transport(
"missing radio model fields in info response".into(),
)
})?;
value["radioModel"] = json_model_summary(&model);
Ok(InfoResult {
value,
radio_model: model,
})
}
pub async fn list(&self) -> Result<ListResult, RadioCodeCalculatorError> {
let mut params = HashMap::new();
params.insert("command".to_string(), "list".to_string());
let mut value = self.post_request(params).await?;
let supported = value
.get("supportedRadioModels")
.and_then(|v| v.as_object())
.cloned()
.unwrap_or_default();
let mut radio_models = Vec::new();
for (radio_model_name, obj) in supported {
if let Some(model) = radio_model_from_response(&radio_model_name, &obj) {
radio_models.push(model);
}
}
value["radioModels"] = json!(radio_models
.iter()
.map(json_model_summary)
.collect::<Vec<_>>());
Ok(ListResult {
value,
radio_models,
})
}
pub async fn post_request(
&self,
params_array: HashMap<String, String>,
) -> Result<Value, RadioCodeCalculatorError> {
let Some(key) = &self._api_key else {
return Err(RadioCodeCalculatorError::InvalidLicense);
};
let mut form = reqwest::multipart::Form::new();
form = form.text("key", key.clone());
for (param, val) in params_array {
form = form.text(param, val);
}
let response = self
.client
.post(&self.api_url)
.multipart(form)
.send()
.await
.map_err(|e| RadioCodeCalculatorError::Transport(e.to_string()))?;
let value: Value = response
.json()
.await
.map_err(|e| RadioCodeCalculatorError::Transport(e.to_string()))?;
let err = value
.get("error")
.and_then(|e| e.as_i64())
.unwrap_or(RadioErrors::ERROR_CONNECTION as i64);
if err == RadioErrors::SUCCESS as i64 {
Ok(value)
} else {
Err(RadioCodeCalculatorError::ApiError(value))
}
}
}
fn radio_model_from_response(name: &str, v: &Value) -> Option<RadioModel> {
let serial_max_len = v.get("serialMaxLen")?.as_u64()? as usize;
let serial_regex = v.get("serialRegexPattern")?.clone();
let extra_max_len = v.get("extraMaxLen").and_then(|x| x.as_u64()).unwrap_or(0) as usize;
let extra_regex = if extra_max_len == 0 {
None
} else {
v.get("extraRegexPattern").cloned()
};
Some(RadioModel::new(
name,
serial_max_len,
serial_regex,
extra_max_len,
extra_regex,
))
}
fn json_model_summary(m: &RadioModel) -> Value {
json!({
"name": m.name,
"serialMaxLen": m.serial_max_len,
"extraMaxLen": m.extra_max_len,
"serialRegexPattern": m.serial_regex_pattern(),
"extraRegexPattern": m.extra_regex_pattern(),
})
}