Split runtime request preparation
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -16,7 +15,7 @@ use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
AdapterResponse, PreparedRequest, ResolvedAuth, RuntimeError, RuntimeLimits, RuntimeOperation,
|
||||
RuntimeRequestContext,
|
||||
RuntimeRequestContext, request_preparation::adapter_prepared_request,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -425,26 +424,6 @@ impl RuntimeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn adapter_request_context(
|
||||
request_context: Option<&RuntimeRequestContext>,
|
||||
) -> crank_core::RuntimeRequestContext {
|
||||
@@ -453,27 +432,6 @@ fn adapter_request_context(
|
||||
.unwrap_or_else(|| crank_core::RuntimeRequestContext::new(String::new(), String::new()))
|
||||
}
|
||||
|
||||
fn adapter_prepared_request(
|
||||
operation: &RuntimeOperation,
|
||||
prepared_request: &PreparedRequest,
|
||||
request_context: Option<&RuntimeRequestContext>,
|
||||
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,
|
||||
&runtime_context_headers(request_context),
|
||||
);
|
||||
prepared_request.timeout_ms = timeout_ms;
|
||||
prepared_request
|
||||
}
|
||||
|
||||
fn map_protocol_adapter_error(
|
||||
operation: &RuntimeOperation,
|
||||
error: crank_core::ProtocolAdapterError,
|
||||
@@ -534,27 +492,6 @@ fn apply_resolved_auth(
|
||||
prepared_request
|
||||
}
|
||||
|
||||
fn merge_headers(
|
||||
static_headers: &BTreeMap<String, String>,
|
||||
execution_headers: &BTreeMap<String, String>,
|
||||
request_headers: &BTreeMap<String, String>,
|
||||
context_headers: &BTreeMap<String, String>,
|
||||
) -> BTreeMap<String, String> {
|
||||
let mut headers = static_headers.clone();
|
||||
headers.extend(execution_headers.clone());
|
||||
headers.extend(request_headers.clone());
|
||||
headers.extend(context_headers.clone());
|
||||
headers
|
||||
}
|
||||
|
||||
fn runtime_context_headers(
|
||||
request_context: Option<&RuntimeRequestContext>,
|
||||
) -> BTreeMap<String, String> {
|
||||
request_context
|
||||
.map(RuntimeRequestContext::outbound_headers)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn try_acquire_limit(
|
||||
limiter: Arc<Semaphore>,
|
||||
kind: &'static str,
|
||||
@@ -663,48 +600,3 @@ fn adapter_response_from_cached(
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ mod limits;
|
||||
mod model;
|
||||
mod rate_limit;
|
||||
mod request_context;
|
||||
mod request_preparation;
|
||||
mod secret_crypto;
|
||||
|
||||
pub use auth::ResolvedAuth;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crank_core::Target;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{PreparedRequest, RuntimeError, RuntimeOperation, RuntimeRequestContext};
|
||||
|
||||
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,
|
||||
request_context: Option<&RuntimeRequestContext>,
|
||||
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,
|
||||
&runtime_context_headers(request_context),
|
||||
);
|
||||
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>,
|
||||
context_headers: &BTreeMap<String, String>,
|
||||
) -> BTreeMap<String, String> {
|
||||
let mut headers = static_headers.clone();
|
||||
headers.extend(execution_headers.clone());
|
||||
headers.extend(request_headers.clone());
|
||||
headers.extend(context_headers.clone());
|
||||
headers
|
||||
}
|
||||
|
||||
fn runtime_context_headers(
|
||||
request_context: Option<&RuntimeRequestContext>,
|
||||
) -> BTreeMap<String, String> {
|
||||
request_context
|
||||
.map(RuntimeRequestContext::outbound_headers)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user