From 75b94f0f3a481f0ef33e0a5801857e6f46347acf Mon Sep 17 00:00:00 2001 From: github-ops Date: Sun, 21 Jun 2026 02:14:30 +0000 Subject: [PATCH] Split runtime request preparation --- crates/crank-runtime/src/executor.rs | 110 +---------------- crates/crank-runtime/src/lib.rs | 1 + .../crank-runtime/src/request_preparation.rs | 113 ++++++++++++++++++ 3 files changed, 115 insertions(+), 109 deletions(-) create mode 100644 crates/crank-runtime/src/request_preparation.rs diff --git a/crates/crank-runtime/src/executor.rs b/crates/crank-runtime/src/executor.rs index 99f1fed..b417776 100644 --- a/crates/crank-runtime/src/executor.rs +++ b/crates/crank-runtime/src/executor.rs @@ -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 { - 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, - execution_headers: &BTreeMap, - request_headers: &BTreeMap, - context_headers: &BTreeMap, -) -> BTreeMap { - 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 { - request_context - .map(RuntimeRequestContext::outbound_headers) - .unwrap_or_default() -} - fn try_acquire_limit( limiter: Arc, kind: &'static str, @@ -663,48 +600,3 @@ fn adapter_response_from_cached( data, }) } - -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), - } -} diff --git a/crates/crank-runtime/src/lib.rs b/crates/crank-runtime/src/lib.rs index a1cf57b..a5c1926 100644 --- a/crates/crank-runtime/src/lib.rs +++ b/crates/crank-runtime/src/lib.rs @@ -10,6 +10,7 @@ mod limits; mod model; mod rate_limit; mod request_context; +mod request_preparation; mod secret_crypto; pub use auth::ResolvedAuth; diff --git a/crates/crank-runtime/src/request_preparation.rs b/crates/crank-runtime/src/request_preparation.rs new file mode 100644 index 0000000..c9eb3ba --- /dev/null +++ b/crates/crank-runtime/src/request_preparation.rs @@ -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 { + 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, + execution_headers: &BTreeMap, + request_headers: &BTreeMap, + context_headers: &BTreeMap, +) -> BTreeMap { + 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 { + request_context + .map(RuntimeRequestContext::outbound_headers) + .unwrap_or_default() +} + +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), + } +}