runtime: switch executor to adapter registry
This commit is contained in:
@@ -151,5 +151,6 @@ Progress:
|
|||||||
- Phase 0 / task `0.7`: введены `CapabilityProfile` и `CommunityCapabilityProfile`, а `admin-api` capability payload теперь собирается через public seam вместо локального literal
|
- Phase 0 / task `0.7`: введены `CapabilityProfile` и `CommunityCapabilityProfile`, а `admin-api` capability payload теперь собирается через public seam вместо локального literal
|
||||||
- Phase 0 / task `0.8`: введены `ProtocolAdapter`, `ProtocolAdapterError`, `AdapterRegistry` и базовые transport-типы (`PreparedRequest`, `AdapterResponse`, `WindowExecutionResult`, `RuntimeRequestContext`) в `crank_core::ext::protocol`
|
- Phase 0 / task `0.8`: введены `ProtocolAdapter`, `ProtocolAdapterError`, `AdapterRegistry` и базовые transport-типы (`PreparedRequest`, `AdapterResponse`, `WindowExecutionResult`, `RuntimeRequestContext`) в `crank_core::ext::protocol`
|
||||||
- Phase 0 / task `0.9`: `crank-adapter-rest::RestAdapter` реализует новый `ProtocolAdapter` contract, а seam дополнен явным `Target` и window parameters для реального adapter wiring
|
- Phase 0 / task `0.9`: `crank-adapter-rest::RestAdapter` реализует новый `ProtocolAdapter` contract, а seam дополнен явным `Target` и window parameters для реального adapter wiring
|
||||||
|
- Phase 0 / task `0.10`: `RuntimeExecutor` переведен с hardcoded adapter fields на `AdapterRegistry`, `crank-runtime` больше не держит protocol feature flags, а общий `PreparedRequest` seam дополнен `timeout_ms` для корректного runtime dispatch
|
||||||
- Phase 0 / task `0.13`: введены `RegistryExtension`, `ExtensionMigration` и `apply_extension_migrations(...)` в `crank-registry` как отдельный public seam для additive private migrations
|
- Phase 0 / task `0.13`: введены `RegistryExtension`, `ExtensionMigration` и `apply_extension_migrations(...)` в `crank-registry` как отдельный public seam для additive private migrations
|
||||||
- backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено
|
- backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено
|
||||||
|
|||||||
@@ -423,6 +423,7 @@ fn runtime_test_failure_code(error: &RuntimeError) -> &'static str {
|
|||||||
RuntimeError::GraphqlAdapter(_) => "runtime_graphql_error",
|
RuntimeError::GraphqlAdapter(_) => "runtime_graphql_error",
|
||||||
RuntimeError::GrpcAdapter(_) => "runtime_grpc_error",
|
RuntimeError::GrpcAdapter(_) => "runtime_grpc_error",
|
||||||
RuntimeError::RestAdapter(_) => "runtime_rest_error",
|
RuntimeError::RestAdapter(_) => "runtime_rest_error",
|
||||||
|
RuntimeError::ProtocolAdapter(_) => "runtime_adapter_error",
|
||||||
RuntimeError::SoapAdapter(_) => "runtime_soap_error",
|
RuntimeError::SoapAdapter(_) => "runtime_soap_error",
|
||||||
RuntimeError::WebsocketAdapter(_) => "runtime_websocket_error",
|
RuntimeError::WebsocketAdapter(_) => "runtime_websocket_error",
|
||||||
RuntimeError::UnsupportedProtocol { .. } => "runtime_protocol_error",
|
RuntimeError::UnsupportedProtocol { .. } => "runtime_protocol_error",
|
||||||
|
|||||||
@@ -4031,6 +4031,7 @@ fn runtime_error_code(error: &RuntimeError) -> &'static str {
|
|||||||
RuntimeError::GraphqlAdapter(_) => "graphql_error",
|
RuntimeError::GraphqlAdapter(_) => "graphql_error",
|
||||||
RuntimeError::GrpcAdapter(_) => "grpc_error",
|
RuntimeError::GrpcAdapter(_) => "grpc_error",
|
||||||
RuntimeError::RestAdapter(_) => "rest_error",
|
RuntimeError::RestAdapter(_) => "rest_error",
|
||||||
|
RuntimeError::ProtocolAdapter(_) => "adapter_error",
|
||||||
RuntimeError::SoapAdapter(_) => "soap_error",
|
RuntimeError::SoapAdapter(_) => "soap_error",
|
||||||
RuntimeError::WebsocketAdapter(_) => "websocket_error",
|
RuntimeError::WebsocketAdapter(_) => "websocket_error",
|
||||||
RuntimeError::UnsupportedProtocol { .. } => "unsupported_protocol",
|
RuntimeError::UnsupportedProtocol { .. } => "unsupported_protocol",
|
||||||
|
|||||||
@@ -2378,6 +2378,7 @@ fn runtime_error_code(error: &RuntimeError) -> &'static str {
|
|||||||
RuntimeError::GraphqlAdapter(_) => "adapter_execution_error",
|
RuntimeError::GraphqlAdapter(_) => "adapter_execution_error",
|
||||||
RuntimeError::GrpcAdapter(_) => "adapter_execution_error",
|
RuntimeError::GrpcAdapter(_) => "adapter_execution_error",
|
||||||
RuntimeError::RestAdapter(_) => "adapter_execution_error",
|
RuntimeError::RestAdapter(_) => "adapter_execution_error",
|
||||||
|
RuntimeError::ProtocolAdapter(_) => "adapter_execution_error",
|
||||||
RuntimeError::SoapAdapter(_) => "adapter_execution_error",
|
RuntimeError::SoapAdapter(_) => "adapter_execution_error",
|
||||||
RuntimeError::WebsocketAdapter(_) => "adapter_execution_error",
|
RuntimeError::WebsocketAdapter(_) => "adapter_execution_error",
|
||||||
RuntimeError::UnsupportedProtocol { .. } => "unsupported_protocol",
|
RuntimeError::UnsupportedProtocol { .. } => "unsupported_protocol",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ impl ProtocolAdapter for RestAdapter {
|
|||||||
query_params: prepared.query_params.clone(),
|
query_params: prepared.query_params.clone(),
|
||||||
headers: prepared.headers.clone(),
|
headers: prepared.headers.clone(),
|
||||||
body: prepared.body.clone(),
|
body: prepared.body.clone(),
|
||||||
timeout_ms: 30_000,
|
timeout_ms: prepared.timeout_ms,
|
||||||
};
|
};
|
||||||
let response = self.execute(target, &request).await?;
|
let response = self.execute(target, &request).await?;
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ impl ProtocolAdapter for RestAdapter {
|
|||||||
query_params: prepared.query_params.clone(),
|
query_params: prepared.query_params.clone(),
|
||||||
headers: prepared.headers.clone(),
|
headers: prepared.headers.clone(),
|
||||||
body: prepared.body.clone(),
|
body: prepared.body.clone(),
|
||||||
timeout_ms: 30_000,
|
timeout_ms: prepared.timeout_ms,
|
||||||
},
|
},
|
||||||
window_duration_ms,
|
window_duration_ms,
|
||||||
max_items: max_items.map(|value| value.saturating_add(1)),
|
max_items: max_items.map(|value| value.saturating_add(1)),
|
||||||
|
|||||||
@@ -71,6 +71,8 @@ pub struct PreparedRequest {
|
|||||||
pub variables: Option<Value>,
|
pub variables: Option<Value>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub body: Option<Value>,
|
pub body: Option<Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub timeout_ms: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
protocol-graphql = []
|
|
||||||
protocol-grpc = []
|
|
||||||
protocol-soap = []
|
|
||||||
protocol-websocket = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes-gcm.workspace = true
|
aes-gcm.workspace = true
|
||||||
|
|||||||
@@ -4,15 +4,6 @@ use crank_mapping::MappingError;
|
|||||||
use crank_schema::SchemaError;
|
use crank_schema::SchemaError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[cfg(feature = "protocol-graphql")]
|
|
||||||
use crank_adapter_graphql::GraphqlAdapterError;
|
|
||||||
#[cfg(feature = "protocol-grpc")]
|
|
||||||
use crank_adapter_grpc::GrpcAdapterError;
|
|
||||||
#[cfg(feature = "protocol-soap")]
|
|
||||||
use crank_adapter_soap::SoapAdapterError;
|
|
||||||
#[cfg(feature = "protocol-websocket")]
|
|
||||||
use crank_adapter_websocket::WebsocketAdapterError;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum RuntimeError {
|
pub enum RuntimeError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
@@ -26,6 +17,8 @@ pub enum RuntimeError {
|
|||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
RestAdapter(#[from] RestAdapterError),
|
RestAdapter(#[from] RestAdapterError),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
|
ProtocolAdapter(String),
|
||||||
|
#[error("{0}")]
|
||||||
SoapAdapter(String),
|
SoapAdapter(String),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
WebsocketAdapter(String),
|
WebsocketAdapter(String),
|
||||||
@@ -58,31 +51,3 @@ pub enum RuntimeError {
|
|||||||
details: String,
|
details: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "protocol-graphql")]
|
|
||||||
impl From<GraphqlAdapterError> for RuntimeError {
|
|
||||||
fn from(value: GraphqlAdapterError) -> Self {
|
|
||||||
Self::GraphqlAdapter(value.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "protocol-grpc")]
|
|
||||||
impl From<GrpcAdapterError> for RuntimeError {
|
|
||||||
fn from(value: GrpcAdapterError) -> Self {
|
|
||||||
Self::GrpcAdapter(value.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "protocol-soap")]
|
|
||||||
impl From<SoapAdapterError> for RuntimeError {
|
|
||||||
fn from(value: SoapAdapterError) -> Self {
|
|
||||||
Self::SoapAdapter(value.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "protocol-websocket")]
|
|
||||||
impl From<WebsocketAdapterError> for RuntimeError {
|
|
||||||
fn from(value: WebsocketAdapterError) -> Self {
|
|
||||||
Self::WebsocketAdapter(value.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||||
use crank_adapter_rest::{RestAdapter, RestRequest, RestWindowRequest};
|
use crank_adapter_rest::RestAdapter;
|
||||||
use crank_core::{
|
use crank_core::{
|
||||||
CachedHeader, CachedResponse, ExecutionMode, HttpMethod, ResponseCacheStore, Target,
|
AdapterRegistry, CachedHeader, CachedResponse, ExecutionMode, HttpMethod, ResponseCacheStore,
|
||||||
TransportBehavior,
|
SharedProtocolAdapter, Target, TransportBehavior,
|
||||||
};
|
};
|
||||||
use serde_json::{Map, Value, json};
|
use serde_json::{Map, Value, json};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
@@ -18,26 +18,9 @@ use crate::{
|
|||||||
RuntimeRequestContext, WindowExecutionResult,
|
RuntimeRequestContext, WindowExecutionResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "protocol-graphql")]
|
|
||||||
use crank_adapter_graphql::{GraphqlAdapter, GraphqlRequest};
|
|
||||||
#[cfg(feature = "protocol-grpc")]
|
|
||||||
use crank_adapter_grpc::{GrpcAdapter, GrpcRequest, GrpcWindowRequest};
|
|
||||||
#[cfg(feature = "protocol-soap")]
|
|
||||||
use crank_adapter_soap::{SoapAdapter, SoapRequest};
|
|
||||||
#[cfg(feature = "protocol-websocket")]
|
|
||||||
use crank_adapter_websocket::{WebsocketAdapter, WebsocketWindowRequest};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RuntimeExecutor {
|
pub struct RuntimeExecutor {
|
||||||
#[cfg(feature = "protocol-graphql")]
|
adapters: AdapterRegistry,
|
||||||
graphql_adapter: GraphqlAdapter,
|
|
||||||
#[cfg(feature = "protocol-grpc")]
|
|
||||||
grpc_adapter: GrpcAdapter,
|
|
||||||
rest_adapter: RestAdapter,
|
|
||||||
#[cfg(feature = "protocol-soap")]
|
|
||||||
soap_adapter: SoapAdapter,
|
|
||||||
#[cfg(feature = "protocol-websocket")]
|
|
||||||
websocket_adapter: WebsocketAdapter,
|
|
||||||
limits: RuntimeLimits,
|
limits: RuntimeLimits,
|
||||||
unary_limiter: Arc<Semaphore>,
|
unary_limiter: Arc<Semaphore>,
|
||||||
window_limiter: Arc<Semaphore>,
|
window_limiter: Arc<Semaphore>,
|
||||||
@@ -59,15 +42,7 @@ impl RuntimeExecutor {
|
|||||||
|
|
||||||
pub fn with_limits(limits: RuntimeLimits) -> Self {
|
pub fn with_limits(limits: RuntimeLimits) -> Self {
|
||||||
Self {
|
Self {
|
||||||
#[cfg(feature = "protocol-graphql")]
|
adapters: community_adapter_registry(),
|
||||||
graphql_adapter: GraphqlAdapter::new(),
|
|
||||||
#[cfg(feature = "protocol-grpc")]
|
|
||||||
grpc_adapter: GrpcAdapter::new(),
|
|
||||||
rest_adapter: RestAdapter::new(),
|
|
||||||
#[cfg(feature = "protocol-soap")]
|
|
||||||
soap_adapter: SoapAdapter::new(),
|
|
||||||
#[cfg(feature = "protocol-websocket")]
|
|
||||||
websocket_adapter: WebsocketAdapter::new(),
|
|
||||||
unary_limiter: Arc::new(Semaphore::new(limits.max_concurrent_unary)),
|
unary_limiter: Arc::new(Semaphore::new(limits.max_concurrent_unary)),
|
||||||
window_limiter: Arc::new(Semaphore::new(limits.max_concurrent_window)),
|
window_limiter: Arc::new(Semaphore::new(limits.max_concurrent_window)),
|
||||||
session_limiter: Arc::new(Semaphore::new(limits.max_concurrent_sessions)),
|
session_limiter: Arc::new(Semaphore::new(limits.max_concurrent_sessions)),
|
||||||
@@ -185,9 +160,6 @@ impl RuntimeExecutor {
|
|||||||
let adapter_response = if matches!(
|
let adapter_response = if matches!(
|
||||||
streaming.transport_behavior,
|
streaming.transport_behavior,
|
||||||
TransportBehavior::ServerStream
|
TransportBehavior::ServerStream
|
||||||
) && matches!(
|
|
||||||
operation.target,
|
|
||||||
Target::Rest(_) | Target::Grpc(_) | Target::Websocket(_)
|
|
||||||
) {
|
) {
|
||||||
self.execute_window_adapter(operation, prepared_request, request_context)
|
self.execute_window_adapter(operation, prepared_request, request_context)
|
||||||
.await?
|
.await?
|
||||||
@@ -322,131 +294,31 @@ impl RuntimeExecutor {
|
|||||||
request_context: Option<&RuntimeRequestContext>,
|
request_context: Option<&RuntimeRequestContext>,
|
||||||
) -> Result<AdapterResponse, RuntimeError> {
|
) -> Result<AdapterResponse, RuntimeError> {
|
||||||
log_runtime_event("adapter.dispatch", operation, request_context);
|
log_runtime_event("adapter.dispatch", operation, request_context);
|
||||||
let context_headers = runtime_context_headers(request_context);
|
let adapter = self.adapter_for(operation)?;
|
||||||
match &operation.target {
|
if !adapter.supports_mode(ExecutionMode::Unary) {
|
||||||
Target::Grpc(target) => {
|
return Err(RuntimeError::UnsupportedExecutionMode {
|
||||||
#[cfg(not(feature = "protocol-grpc"))]
|
|
||||||
{
|
|
||||||
let _ = target;
|
|
||||||
return Err(RuntimeError::UnsupportedProtocol {
|
|
||||||
protocol: operation.protocol,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#[cfg(feature = "protocol-grpc")]
|
|
||||||
{
|
|
||||||
let request = GrpcRequest {
|
|
||||||
headers: merge_headers(
|
|
||||||
&BTreeMap::new(),
|
|
||||||
&operation.execution_config.headers,
|
|
||||||
&prepared_request.headers,
|
|
||||||
&context_headers,
|
|
||||||
),
|
|
||||||
body: prepared_request
|
|
||||||
.grpc
|
|
||||||
.clone()
|
|
||||||
.unwrap_or(Value::Object(Map::new())),
|
|
||||||
timeout_ms: operation.execution_config.timeout_ms,
|
|
||||||
};
|
|
||||||
let response = self.grpc_adapter.execute(target, &request).await?;
|
|
||||||
|
|
||||||
Ok(AdapterResponse {
|
|
||||||
status_code: response.status_code,
|
|
||||||
headers: response.headers,
|
|
||||||
body: response.body.clone(),
|
|
||||||
data: response.body,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Target::Graphql(target) => {
|
|
||||||
#[cfg(not(feature = "protocol-graphql"))]
|
|
||||||
{
|
|
||||||
let _ = target;
|
|
||||||
return Err(RuntimeError::UnsupportedProtocol {
|
|
||||||
protocol: operation.protocol,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#[cfg(feature = "protocol-graphql")]
|
|
||||||
{
|
|
||||||
let request = GraphqlRequest {
|
|
||||||
headers: merge_headers(
|
|
||||||
&BTreeMap::new(),
|
|
||||||
&operation.execution_config.headers,
|
|
||||||
&prepared_request.headers,
|
|
||||||
&context_headers,
|
|
||||||
),
|
|
||||||
variables: prepared_request.variables.clone(),
|
|
||||||
timeout_ms: operation.execution_config.timeout_ms,
|
|
||||||
};
|
|
||||||
let response = self.graphql_adapter.execute(target, &request).await?;
|
|
||||||
|
|
||||||
Ok(AdapterResponse {
|
|
||||||
status_code: response.status_code,
|
|
||||||
headers: response.headers,
|
|
||||||
body: response.body,
|
|
||||||
data: response.data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Target::Rest(target) => {
|
|
||||||
let request = RestRequest {
|
|
||||||
path_params: prepared_request.path_params.clone(),
|
|
||||||
query_params: prepared_request.query_params.clone(),
|
|
||||||
headers: merge_headers(
|
|
||||||
&target.static_headers,
|
|
||||||
&operation.execution_config.headers,
|
|
||||||
&prepared_request.headers,
|
|
||||||
&context_headers,
|
|
||||||
),
|
|
||||||
body: prepared_request.body.clone(),
|
|
||||||
timeout_ms: operation.execution_config.timeout_ms,
|
|
||||||
};
|
|
||||||
let response = self.rest_adapter.execute(target, &request).await?;
|
|
||||||
|
|
||||||
Ok(AdapterResponse {
|
|
||||||
status_code: response.status_code,
|
|
||||||
headers: response.headers,
|
|
||||||
body: response.body,
|
|
||||||
data: Value::Null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Target::Soap(target) => {
|
|
||||||
#[cfg(not(feature = "protocol-soap"))]
|
|
||||||
{
|
|
||||||
let _ = target;
|
|
||||||
Err(RuntimeError::UnsupportedProtocol {
|
|
||||||
protocol: operation.protocol,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#[cfg(feature = "protocol-soap")]
|
|
||||||
{
|
|
||||||
let request = SoapRequest {
|
|
||||||
headers: merge_headers(
|
|
||||||
&BTreeMap::new(),
|
|
||||||
&operation.execution_config.headers,
|
|
||||||
&prepared_request.headers,
|
|
||||||
&context_headers,
|
|
||||||
),
|
|
||||||
body: prepared_request
|
|
||||||
.body
|
|
||||||
.clone()
|
|
||||||
.unwrap_or(Value::Object(Map::new())),
|
|
||||||
timeout_ms: operation.execution_config.timeout_ms,
|
|
||||||
};
|
|
||||||
let response = self.soap_adapter.execute(target, &request).await?;
|
|
||||||
|
|
||||||
Ok(AdapterResponse {
|
|
||||||
status_code: response.status_code,
|
|
||||||
headers: response.headers,
|
|
||||||
body: response.body,
|
|
||||||
data: Value::Null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Target::Websocket(_) => Err(RuntimeError::UnsupportedExecutionMode {
|
|
||||||
operation_id: operation.operation_id.as_str().to_owned(),
|
operation_id: operation.operation_id.as_str().to_owned(),
|
||||||
mode: ExecutionMode::Unary,
|
mode: ExecutionMode::Unary,
|
||||||
}),
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let prepared_request = adapter_prepared_request(
|
||||||
|
operation,
|
||||||
|
&prepared_request,
|
||||||
|
request_context,
|
||||||
|
operation.execution_config.timeout_ms,
|
||||||
|
);
|
||||||
|
let adapter_context = adapter_request_context(request_context);
|
||||||
|
|
||||||
|
adapter
|
||||||
|
.invoke_unary(
|
||||||
|
&operation.target,
|
||||||
|
&prepared_request.into(),
|
||||||
|
&adapter_context,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(Into::into)
|
||||||
|
.map_err(|error| map_protocol_adapter_error(operation, error))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_window_adapter(
|
async fn execute_window_adapter(
|
||||||
@@ -456,145 +328,50 @@ impl RuntimeExecutor {
|
|||||||
request_context: Option<&RuntimeRequestContext>,
|
request_context: Option<&RuntimeRequestContext>,
|
||||||
) -> Result<AdapterResponse, RuntimeError> {
|
) -> Result<AdapterResponse, RuntimeError> {
|
||||||
log_runtime_event("window.adapter.dispatch", operation, request_context);
|
log_runtime_event("window.adapter.dispatch", operation, request_context);
|
||||||
let context_headers = runtime_context_headers(request_context);
|
|
||||||
match &operation.target {
|
|
||||||
Target::Rest(target) => {
|
|
||||||
let Some(streaming) = operation.execution_config.streaming.as_ref() else {
|
let Some(streaming) = operation.execution_config.streaming.as_ref() else {
|
||||||
return Err(RuntimeError::MissingStreamingConfig {
|
return Err(RuntimeError::MissingStreamingConfig {
|
||||||
operation_id: operation.operation_id.as_str().to_owned(),
|
operation_id: operation.operation_id.as_str().to_owned(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
let request = RestWindowRequest {
|
let adapter = self.adapter_for(operation)?;
|
||||||
request: RestRequest {
|
if !adapter.supports_mode(ExecutionMode::Window) {
|
||||||
path_params: prepared_request.path_params.clone(),
|
return Err(RuntimeError::UnsupportedExecutionMode {
|
||||||
query_params: prepared_request.query_params.clone(),
|
|
||||||
headers: merge_headers(
|
|
||||||
&target.static_headers,
|
|
||||||
&operation.execution_config.headers,
|
|
||||||
&prepared_request.headers,
|
|
||||||
&context_headers,
|
|
||||||
),
|
|
||||||
body: prepared_request.body.clone(),
|
|
||||||
timeout_ms: streaming
|
|
||||||
.upstream_timeout_ms
|
|
||||||
.unwrap_or(operation.execution_config.timeout_ms),
|
|
||||||
},
|
|
||||||
window_duration_ms: streaming.window_duration_ms.unwrap_or_default(),
|
|
||||||
max_items: streaming.max_items.map(|value| value.saturating_add(1)),
|
|
||||||
};
|
|
||||||
let response = self.rest_adapter.execute_window(target, &request).await?;
|
|
||||||
|
|
||||||
Ok(AdapterResponse {
|
|
||||||
status_code: response.status_code,
|
|
||||||
headers: response.headers,
|
|
||||||
body: response.body,
|
|
||||||
data: Value::Null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Target::Grpc(target) => {
|
|
||||||
#[cfg(not(feature = "protocol-grpc"))]
|
|
||||||
{
|
|
||||||
let _ = target;
|
|
||||||
return Err(RuntimeError::UnsupportedProtocol {
|
|
||||||
protocol: operation.protocol,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
#[cfg(feature = "protocol-grpc")]
|
|
||||||
{
|
|
||||||
let Some(streaming) = operation.execution_config.streaming.as_ref() else {
|
|
||||||
return Err(RuntimeError::MissingStreamingConfig {
|
|
||||||
operation_id: operation.operation_id.as_str().to_owned(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let request = GrpcWindowRequest {
|
|
||||||
request: GrpcRequest {
|
|
||||||
headers: merge_headers(
|
|
||||||
&BTreeMap::new(),
|
|
||||||
&operation.execution_config.headers,
|
|
||||||
&prepared_request.headers,
|
|
||||||
&context_headers,
|
|
||||||
),
|
|
||||||
body: prepared_request
|
|
||||||
.grpc
|
|
||||||
.clone()
|
|
||||||
.unwrap_or(Value::Object(Map::new())),
|
|
||||||
timeout_ms: streaming
|
|
||||||
.upstream_timeout_ms
|
|
||||||
.unwrap_or(operation.execution_config.timeout_ms),
|
|
||||||
},
|
|
||||||
window_duration_ms: streaming.window_duration_ms.unwrap_or_default(),
|
|
||||||
max_items: streaming.max_items.map(|value| value.saturating_add(1)),
|
|
||||||
};
|
|
||||||
let response = self.grpc_adapter.execute_window(target, &request).await?;
|
|
||||||
|
|
||||||
Ok(AdapterResponse {
|
|
||||||
status_code: response.status_code,
|
|
||||||
headers: response.headers,
|
|
||||||
body: response.body,
|
|
||||||
data: Value::Null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Target::Websocket(target) => {
|
|
||||||
#[cfg(not(feature = "protocol-websocket"))]
|
|
||||||
{
|
|
||||||
let _ = target;
|
|
||||||
Err(RuntimeError::UnsupportedProtocol {
|
|
||||||
protocol: operation.protocol,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#[cfg(feature = "protocol-websocket")]
|
|
||||||
{
|
|
||||||
let Some(streaming) = operation.execution_config.streaming.as_ref() else {
|
|
||||||
return Err(RuntimeError::MissingStreamingConfig {
|
|
||||||
operation_id: operation.operation_id.as_str().to_owned(),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let websocket_options = operation
|
|
||||||
.execution_config
|
|
||||||
.protocol_options
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|options| options.websocket.as_ref());
|
|
||||||
let request = WebsocketWindowRequest {
|
|
||||||
headers: merge_headers(
|
|
||||||
&BTreeMap::new(),
|
|
||||||
&operation.execution_config.headers,
|
|
||||||
&prepared_request.headers,
|
|
||||||
&context_headers,
|
|
||||||
),
|
|
||||||
window_duration_ms: streaming.window_duration_ms.unwrap_or_default(),
|
|
||||||
max_items: streaming.max_items.map(|value| value.saturating_add(1)),
|
|
||||||
heartbeat_interval_ms: websocket_options
|
|
||||||
.and_then(|options| options.heartbeat_interval_ms),
|
|
||||||
reconnect_max_attempts: websocket_options
|
|
||||||
.and_then(|options| options.reconnect_max_attempts)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
reconnect_backoff_ms: websocket_options
|
|
||||||
.and_then(|options| options.reconnect_backoff_ms)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
};
|
|
||||||
let response = self
|
|
||||||
.websocket_adapter
|
|
||||||
.execute_window(target, &request)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(AdapterResponse {
|
|
||||||
status_code: response.status_code,
|
|
||||||
headers: response.headers,
|
|
||||||
body: response.body,
|
|
||||||
data: Value::Null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Target::Soap(_) => Err(RuntimeError::UnsupportedExecutionMode {
|
|
||||||
operation_id: operation.operation_id.as_str().to_owned(),
|
operation_id: operation.operation_id.as_str().to_owned(),
|
||||||
mode: ExecutionMode::Window,
|
mode: ExecutionMode::Window,
|
||||||
}),
|
});
|
||||||
_ => {
|
}
|
||||||
self.execute_adapter(operation, prepared_request, request_context)
|
|
||||||
|
let prepared_request = adapter_prepared_request(
|
||||||
|
operation,
|
||||||
|
&prepared_request,
|
||||||
|
request_context,
|
||||||
|
streaming
|
||||||
|
.upstream_timeout_ms
|
||||||
|
.unwrap_or(operation.execution_config.timeout_ms),
|
||||||
|
);
|
||||||
|
let adapter_context = adapter_request_context(request_context);
|
||||||
|
let result = adapter
|
||||||
|
.invoke_window(
|
||||||
|
&operation.target,
|
||||||
|
&prepared_request.into(),
|
||||||
|
streaming.window_duration_ms.unwrap_or_default(),
|
||||||
|
streaming.max_items,
|
||||||
|
&adapter_context,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
.map_err(|error| map_protocol_adapter_error(operation, error))?;
|
||||||
}
|
|
||||||
|
Ok(AdapterResponse {
|
||||||
|
status_code: 200,
|
||||||
|
headers: BTreeMap::new(),
|
||||||
|
body: json!({
|
||||||
|
"summary": result.summary,
|
||||||
|
"items": result.items,
|
||||||
|
"cursor": result.cursor,
|
||||||
|
"done": result.window_complete,
|
||||||
|
}),
|
||||||
|
data: Value::Null,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn acquire_unary_permit(
|
fn acquire_unary_permit(
|
||||||
@@ -732,6 +509,72 @@ impl PreparedRequest {
|
|||||||
grpc: non_empty_payload(request.get("grpc").cloned()),
|
grpc: non_empty_payload(request.get("grpc").cloned()),
|
||||||
variables: non_empty_payload(request.get("variables").cloned()),
|
variables: non_empty_payload(request.get("variables").cloned()),
|
||||||
body: request.get("body").and_then(non_empty_body).cloned(),
|
body: request.get("body").and_then(non_empty_body).cloned(),
|
||||||
|
timeout_ms: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn community_adapter_registry() -> AdapterRegistry {
|
||||||
|
AdapterRegistry::empty().register(Arc::new(RestAdapter::new()) as SharedProtocolAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn adapter_request_context(
|
||||||
|
request_context: Option<&RuntimeRequestContext>,
|
||||||
|
) -> crank_core::RuntimeRequestContext {
|
||||||
|
request_context
|
||||||
|
.map(Into::into)
|
||||||
|
.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 empty_headers = BTreeMap::new();
|
||||||
|
let static_headers = match &operation.target {
|
||||||
|
Target::Rest(target) => &target.static_headers,
|
||||||
|
_ => &empty_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,
|
||||||
|
) -> RuntimeError {
|
||||||
|
match error {
|
||||||
|
crank_core::ProtocolAdapterError::UnsupportedMode { mode, .. } => {
|
||||||
|
RuntimeError::UnsupportedExecutionMode {
|
||||||
|
operation_id: operation.operation_id.as_str().to_owned(),
|
||||||
|
mode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
crank_core::ProtocolAdapterError::Message(message) => RuntimeError::ProtocolAdapter(
|
||||||
|
format!("operation {}: {message}", operation.operation_id),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimeExecutor {
|
||||||
|
fn adapter_for(
|
||||||
|
&self,
|
||||||
|
operation: &RuntimeOperation,
|
||||||
|
) -> Result<SharedProtocolAdapter, RuntimeError> {
|
||||||
|
self.adapters
|
||||||
|
.get(operation.protocol)
|
||||||
|
.ok_or(RuntimeError::UnsupportedProtocol {
|
||||||
|
protocol: operation.protocol,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ pub struct PreparedRequest {
|
|||||||
pub variables: Option<Value>,
|
pub variables: Option<Value>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub body: Option<Value>,
|
pub body: Option<Value>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub timeout_ms: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
@@ -63,3 +65,42 @@ pub struct AdapterResponse {
|
|||||||
pub body: Value,
|
pub body: Value,
|
||||||
pub data: Value,
|
pub data: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<PreparedRequest> for crank_core::PreparedRequest {
|
||||||
|
fn from(value: PreparedRequest) -> Self {
|
||||||
|
Self {
|
||||||
|
path_params: value.path_params,
|
||||||
|
query_params: value.query_params,
|
||||||
|
headers: value.headers,
|
||||||
|
grpc: value.grpc,
|
||||||
|
variables: value.variables,
|
||||||
|
body: value.body,
|
||||||
|
timeout_ms: value.timeout_ms,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crank_core::PreparedRequest> for PreparedRequest {
|
||||||
|
fn from(value: crank_core::PreparedRequest) -> Self {
|
||||||
|
Self {
|
||||||
|
path_params: value.path_params,
|
||||||
|
query_params: value.query_params,
|
||||||
|
headers: value.headers,
|
||||||
|
grpc: value.grpc,
|
||||||
|
variables: value.variables,
|
||||||
|
body: value.body,
|
||||||
|
timeout_ms: value.timeout_ms,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<crank_core::AdapterResponse> for AdapterResponse {
|
||||||
|
fn from(value: crank_core::AdapterResponse) -> Self {
|
||||||
|
Self {
|
||||||
|
status_code: value.status_code,
|
||||||
|
headers: value.headers,
|
||||||
|
body: value.body,
|
||||||
|
data: value.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -51,6 +51,34 @@ impl RuntimeRequestContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ResponseCacheScope> for crank_core::ResponseCacheScope {
|
||||||
|
fn from(value: ResponseCacheScope) -> Self {
|
||||||
|
Self {
|
||||||
|
workspace_key: value.workspace_key,
|
||||||
|
agent_key: value.agent_key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ResponseCacheScope> for crank_core::ResponseCacheScope {
|
||||||
|
fn from(value: &ResponseCacheScope) -> Self {
|
||||||
|
Self {
|
||||||
|
workspace_key: value.workspace_key.clone(),
|
||||||
|
agent_key: value.agent_key.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&RuntimeRequestContext> for crank_core::RuntimeRequestContext {
|
||||||
|
fn from(value: &RuntimeRequestContext) -> Self {
|
||||||
|
Self {
|
||||||
|
request_id: value.request_id.clone(),
|
||||||
|
correlation_id: value.correlation_id.clone(),
|
||||||
|
response_cache_scope: value.response_cache_scope.as_ref().map(Into::into),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::RuntimeRequestContext;
|
use super::RuntimeRequestContext;
|
||||||
|
|||||||
@@ -11,3 +11,16 @@ pub struct WindowExecutionResult {
|
|||||||
pub truncated: bool,
|
pub truncated: bool,
|
||||||
pub has_more: bool,
|
pub has_more: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crank_core::WindowExecutionResult> for WindowExecutionResult {
|
||||||
|
fn from(value: crank_core::WindowExecutionResult) -> Self {
|
||||||
|
Self {
|
||||||
|
summary: value.summary,
|
||||||
|
items: value.items,
|
||||||
|
cursor: value.cursor,
|
||||||
|
window_complete: value.window_complete,
|
||||||
|
truncated: value.truncated,
|
||||||
|
has_more: value.has_more,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user