use std::collections::HashMap;
use serde_json::Value;
use crate::api::client::ApiClient;
use crate::api::error::ApiError;
use crate::model::types::Operation;
pub struct OperationExecutor<'a> {
client: &'a ApiClient,
}
impl<'a> OperationExecutor<'a> {
pub fn new(client: &'a ApiClient) -> Self {
Self { client }
}
pub async fn execute(
&self,
operation: &Operation,
param_values: &HashMap<String, Value>,
) -> Result<Value, ApiError> {
let (path_params, body_value) = build_request(operation, param_values);
self.client
.call(
operation.method(),
operation.path(),
Some(&path_params),
body_value.as_ref(),
)
.await
}
pub async fn execute_all_pages(
&self,
operation: &Operation,
param_values: &HashMap<String, Value>,
) -> Result<Vec<Value>, ApiError> {
let pagination = match &operation.pagination {
Some(p) => p,
None => return self.execute(operation, param_values).await.map(|v| vec![v]),
};
let mut all_items = Vec::new();
let mut current_page = 1u64;
let page_size = pagination.default_page_size as u64;
loop {
let mut page_params = param_values.clone();
page_params.insert(
pagination.page_size_param.clone(),
Value::Number(page_size.into()),
);
page_params.insert(
pagination.page_param.clone(),
Value::Number(current_page.into()),
);
let result = self.execute(operation, &page_params).await?;
let items = result
.get(&pagination.data_field)
.and_then(|v| v.as_array())
.cloned()
.unwrap_or_default();
let count = items.len();
all_items.extend(items);
if count < page_size as usize {
break;
}
current_page += 1;
}
Ok(all_items)
}
}
pub fn build_request(
operation: &Operation,
param_values: &HashMap<String, Value>,
) -> (HashMap<String, String>, Option<Value>) {
let mut path_params = HashMap::new();
let mut body = serde_json::Map::new();
for (key, value) in &operation.body_defaults {
body.insert(key.clone(), value.clone());
}
for param in &operation.params {
let value = param_values.get(¶m.name);
if param.is_path_param() {
if let Some(val) = value {
let str_val = match val {
Value::String(s) => s.clone(),
other => other.to_string(),
};
path_params.insert(param.name.clone(), str_val);
}
} else if let Some(val) = value {
if !val.is_null() {
body.insert(param.name.clone(), val.clone());
}
}
}
let declared: std::collections::HashSet<&str> =
operation.params.iter().map(|p| p.name.as_str()).collect();
for (key, val) in param_values {
if declared.contains(key.as_str()) || val.is_null() {
continue;
}
body.insert(key.clone(), val.clone());
}
if let Some(ref transform) = operation.body_transform {
apply_body_transform(&mut body, transform);
}
let body_value = if body.is_empty() && operation.method() == "GET" {
None
} else {
Some(Value::Object(body))
};
(path_params, body_value)
}
fn apply_body_transform(body: &mut serde_json::Map<String, Value>, transform: &Value) {
if let Some(infer) = transform.get("infer").and_then(|v| v.as_object()) {
for (field_name, rule) in infer {
let when_param = rule.get("when").and_then(|v| v.as_str()).unwrap_or("");
if !when_param.is_empty() && body.contains_key(when_param) {
if !body.contains_key(field_name) {
if let Some(value) = rule.get("value") {
body.insert(field_name.clone(), value.clone());
}
}
}
}
}
if let Some(compose) = transform.get("compose").and_then(|v| v.as_object()) {
for (field_name, rule) in compose {
let when_param = rule.get("when").and_then(|v| v.as_str()).unwrap_or("");
if !when_param.is_empty() && !body.contains_key(when_param) {
continue;
}
if let Some(template) = rule.get("template") {
let resolved = resolve_template(template, body);
body.insert(field_name.clone(), resolved);
}
if let Some(consume) = rule.get("consume").and_then(|v| v.as_array()) {
for param in consume {
if let Some(name) = param.as_str() {
body.remove(name);
}
}
}
}
}
}
fn resolve_template(template: &Value, body: &serde_json::Map<String, Value>) -> Value {
match template {
Value::String(s) => {
if s.starts_with('{') && s.ends_with('}') && s.len() > 2 {
let param_name = &s[1..s.len() - 1];
if let Some(val) = body.get(param_name) {
return val.clone();
}
}
let mut result = s.clone();
for (key, val) in body {
let str_val = match val {
Value::String(v) => v.clone(),
Value::Number(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
_ => continue,
};
result = result.replace(&format!("{{{}}}", key), &str_val);
}
Value::String(result)
}
Value::Array(arr) => Value::Array(
arr.iter()
.map(|item| resolve_template(item, body))
.collect(),
),
Value::Object(obj) => {
let mut new_obj = serde_json::Map::new();
for (k, v) in obj {
new_obj.insert(k.clone(), resolve_template(v, body));
}
Value::Object(new_obj)
}
other => other.clone(),
}
}