0e8f1ca03a
Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
102 lines
3.1 KiB
Rust
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),
|
|
}
|
|
}
|