Files
crank/crates/crank-runtime/src/request_preparation.rs
T
bsodfather 0e8f1ca03a
CI / Rust Checks (push) Failing after 4m28s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Community Image Smoke (push) Has been skipped
CI / Deploy (push) Has been skipped
наблюдаемость: завершить базовый контур Community
Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
2026-07-31 01:01:14 +03:00

102 lines
3.1 KiB
Rust

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<Self, RuntimeError> {
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<String, String>,
execution_headers: &BTreeMap<String, String>,
request_headers: &BTreeMap<String, String>,
) -> BTreeMap<String, String> {
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<BTreeMap<String, String>, 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::<Result<BTreeMap<_, _>, _>>()
.map_err(|reason| RuntimeError::InvalidPreparedRequest {
field: field_name.to_owned(),
reason,
})
}
fn stringify_value(value: &Value) -> Result<String, String> {
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),
}
}