use std::collections::BTreeMap; use crank_core::Target; use serde_json::Value; use crate::{PreparedRequest, RuntimeError, RuntimeOperation}; impl PreparedRequest { pub fn from_mapping_output(mapped: &Value) -> Result { let request = mapped .get("request") .ok_or_else(|| RuntimeError::InvalidPreparedRequest { field: "request".to_owned(), reason: "mapped input must contain request root".to_owned(), })?; Ok(Self { path_params: read_string_map(request.get("path"), "request.path")?, query_params: read_string_map(request.get("query"), "request.query")?, headers: read_string_map(request.get("headers"), "request.headers")?, body: request.get("body").and_then(non_empty_body).cloned(), timeout_ms: 0, }) } } pub(crate) fn adapter_prepared_request( operation: &RuntimeOperation, prepared_request: &PreparedRequest, timeout_ms: u64, ) -> PreparedRequest { let static_headers = match &operation.target { Target::Rest(target) => &target.static_headers, }; let mut prepared_request = prepared_request.clone(); prepared_request.headers = merge_headers( static_headers, &operation.execution_config.headers, &prepared_request.headers, ); prepared_request.timeout_ms = timeout_ms; prepared_request } fn merge_headers( static_headers: &BTreeMap, execution_headers: &BTreeMap, request_headers: &BTreeMap, ) -> BTreeMap { let mut headers = static_headers.clone(); headers.extend(execution_headers.clone()); headers.extend(request_headers.clone()); headers } fn read_string_map( value: Option<&Value>, field_name: &str, ) -> Result, RuntimeError> { let Some(value) = value else { return Ok(BTreeMap::new()); }; let Some(object) = value.as_object() else { return Err(RuntimeError::InvalidPreparedRequest { field: field_name.to_owned(), reason: "must be an object".to_owned(), }); }; object .iter() .map(|(key, value)| stringify_value(value).map(|value| (key.clone(), value))) .collect::, _>>() .map_err(|reason| RuntimeError::InvalidPreparedRequest { field: field_name.to_owned(), reason, }) } fn stringify_value(value: &Value) -> Result { match value { Value::Null => Ok("null".to_owned()), Value::Bool(value) => Ok(value.to_string()), Value::Number(value) => Ok(value.to_string()), Value::String(value) => Ok(value.clone()), Value::Array(_) | Value::Object(_) => { Err("request path/query/headers accept only scalar values".to_owned()) } } } fn non_empty_body(value: &Value) -> Option<&Value> { match value { Value::Null => None, Value::Object(object) if object.is_empty() => None, _ => Some(value), } }