adapter: implement protocol trait for rest
This commit is contained in:
Generated
+1
@@ -384,6 +384,7 @@ dependencies = [
|
|||||||
name = "crank-adapter-rest"
|
name = "crank-adapter-rest"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
"crank-core",
|
"crank-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
|||||||
@@ -150,5 +150,6 @@ Progress:
|
|||||||
- Phase 0 / task `0.6`: введены `AuditSink`, `NoopAuditSink`, `AuditEventId` и базовые audit-типы в public seam `crank_core::ext::audit`
|
- Phase 0 / task `0.6`: введены `AuditSink`, `NoopAuditSink`, `AuditEventId` и базовые audit-типы в public seam `crank_core::ext::audit`
|
||||||
- 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.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 не изменено
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ rust-version.workspace = true
|
|||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-trait = "0.1"
|
||||||
crank-core = { path = "../crank-core" }
|
crank-core = { path = "../crank-core" }
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
reqwest = { workspace = true, features = ["stream"] }
|
reqwest = { workspace = true, features = ["stream"] }
|
||||||
|
|||||||
@@ -3,6 +3,121 @@ mod error;
|
|||||||
mod model;
|
mod model;
|
||||||
mod sse;
|
mod sse;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use crank_core::{
|
||||||
|
AdapterResponse, ExecutionMode, PreparedRequest, Protocol, ProtocolAdapter,
|
||||||
|
ProtocolAdapterError, RestTarget, RuntimeRequestContext, Target, WindowExecutionResult,
|
||||||
|
};
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
pub use client::RestAdapter;
|
pub use client::RestAdapter;
|
||||||
pub use error::RestAdapterError;
|
pub use error::RestAdapterError;
|
||||||
pub use model::{RestRequest, RestResponse, RestWindowRequest, RestWindowResponse};
|
pub use model::{RestRequest, RestResponse, RestWindowRequest, RestWindowResponse};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ProtocolAdapter for RestAdapter {
|
||||||
|
fn protocol(&self) -> Protocol {
|
||||||
|
Protocol::Rest
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_mode(&self, mode: ExecutionMode) -> bool {
|
||||||
|
matches!(mode, ExecutionMode::Unary | ExecutionMode::Window)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn invoke_unary(
|
||||||
|
&self,
|
||||||
|
target: &Target,
|
||||||
|
prepared: &PreparedRequest,
|
||||||
|
_context: &RuntimeRequestContext,
|
||||||
|
) -> Result<AdapterResponse, ProtocolAdapterError> {
|
||||||
|
let target = rest_target(target)?;
|
||||||
|
let request = RestRequest {
|
||||||
|
path_params: prepared.path_params.clone(),
|
||||||
|
query_params: prepared.query_params.clone(),
|
||||||
|
headers: prepared.headers.clone(),
|
||||||
|
body: prepared.body.clone(),
|
||||||
|
timeout_ms: 30_000,
|
||||||
|
};
|
||||||
|
let response = self.execute(target, &request).await?;
|
||||||
|
|
||||||
|
Ok(AdapterResponse {
|
||||||
|
status_code: response.status_code,
|
||||||
|
headers: response.headers,
|
||||||
|
data: response.body.clone(),
|
||||||
|
body: response.body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn invoke_window(
|
||||||
|
&self,
|
||||||
|
target: &Target,
|
||||||
|
prepared: &PreparedRequest,
|
||||||
|
window_duration_ms: u64,
|
||||||
|
max_items: Option<u32>,
|
||||||
|
_context: &RuntimeRequestContext,
|
||||||
|
) -> Result<WindowExecutionResult, ProtocolAdapterError> {
|
||||||
|
let target = rest_target(target)?;
|
||||||
|
let request = RestWindowRequest {
|
||||||
|
request: RestRequest {
|
||||||
|
path_params: prepared.path_params.clone(),
|
||||||
|
query_params: prepared.query_params.clone(),
|
||||||
|
headers: prepared.headers.clone(),
|
||||||
|
body: prepared.body.clone(),
|
||||||
|
timeout_ms: 30_000,
|
||||||
|
},
|
||||||
|
window_duration_ms,
|
||||||
|
max_items: max_items.map(|value| value.saturating_add(1)),
|
||||||
|
};
|
||||||
|
let response = self.execute_window(target, &request).await?;
|
||||||
|
Ok(window_result_from_response(response.body, max_items))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rest_target(target: &Target) -> Result<&RestTarget, ProtocolAdapterError> {
|
||||||
|
match target {
|
||||||
|
Target::Rest(target) => Ok(target),
|
||||||
|
other => Err(ProtocolAdapterError::Message(format!(
|
||||||
|
"rest adapter cannot handle target {other:?}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_result_from_response(body: Value, max_items: Option<u32>) -> WindowExecutionResult {
|
||||||
|
let done = body.get("done").and_then(Value::as_bool).unwrap_or(true);
|
||||||
|
let mut items = body
|
||||||
|
.get("items")
|
||||||
|
.and_then(Value::as_array)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mut truncated = false;
|
||||||
|
let mut has_more = !done;
|
||||||
|
|
||||||
|
if let Some(max_items) = max_items.map(|value| value as usize) {
|
||||||
|
if items.len() > max_items {
|
||||||
|
items.truncate(max_items);
|
||||||
|
truncated = true;
|
||||||
|
has_more = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let summary = body
|
||||||
|
.get("summary")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| Value::Object(Map::new()));
|
||||||
|
let cursor = body.get("cursor").cloned();
|
||||||
|
|
||||||
|
WindowExecutionResult {
|
||||||
|
summary,
|
||||||
|
items,
|
||||||
|
cursor,
|
||||||
|
window_complete: !has_more,
|
||||||
|
truncated,
|
||||||
|
has_more,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RestAdapterError> for ProtocolAdapterError {
|
||||||
|
fn from(value: RestAdapterError) -> Self {
|
||||||
|
ProtocolAdapterError::Message(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use async_trait::async_trait;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{ExecutionMode, Protocol, StreamSession};
|
use crate::{ExecutionMode, Protocol, StreamSession, Target};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
||||||
pub struct ResponseCacheScope {
|
pub struct ResponseCacheScope {
|
||||||
@@ -112,13 +112,17 @@ pub trait ProtocolAdapter: Send + Sync {
|
|||||||
|
|
||||||
async fn invoke_unary(
|
async fn invoke_unary(
|
||||||
&self,
|
&self,
|
||||||
|
target: &Target,
|
||||||
prepared: &PreparedRequest,
|
prepared: &PreparedRequest,
|
||||||
context: &RuntimeRequestContext,
|
context: &RuntimeRequestContext,
|
||||||
) -> Result<AdapterResponse, ProtocolAdapterError>;
|
) -> Result<AdapterResponse, ProtocolAdapterError>;
|
||||||
|
|
||||||
async fn invoke_window(
|
async fn invoke_window(
|
||||||
&self,
|
&self,
|
||||||
|
_target: &Target,
|
||||||
_prepared: &PreparedRequest,
|
_prepared: &PreparedRequest,
|
||||||
|
_window_duration_ms: u64,
|
||||||
|
_max_items: Option<u32>,
|
||||||
_context: &RuntimeRequestContext,
|
_context: &RuntimeRequestContext,
|
||||||
) -> Result<WindowExecutionResult, ProtocolAdapterError> {
|
) -> Result<WindowExecutionResult, ProtocolAdapterError> {
|
||||||
Err(ProtocolAdapterError::UnsupportedMode {
|
Err(ProtocolAdapterError::UnsupportedMode {
|
||||||
@@ -129,6 +133,7 @@ pub trait ProtocolAdapter: Send + Sync {
|
|||||||
|
|
||||||
async fn open_session(
|
async fn open_session(
|
||||||
&self,
|
&self,
|
||||||
|
_target: &Target,
|
||||||
_prepared: &PreparedRequest,
|
_prepared: &PreparedRequest,
|
||||||
_context: &RuntimeRequestContext,
|
_context: &RuntimeRequestContext,
|
||||||
) -> Result<StreamSession, ProtocolAdapterError> {
|
) -> Result<StreamSession, ProtocolAdapterError> {
|
||||||
@@ -184,7 +189,7 @@ mod tests {
|
|||||||
AdapterRegistry, AdapterResponse, PreparedRequest, ProtocolAdapter, ProtocolAdapterError,
|
AdapterRegistry, AdapterResponse, PreparedRequest, ProtocolAdapter, ProtocolAdapterError,
|
||||||
RuntimeRequestContext,
|
RuntimeRequestContext,
|
||||||
};
|
};
|
||||||
use crate::{ExecutionMode, Protocol};
|
use crate::{ExecutionMode, HttpMethod, Protocol, RestTarget, Target};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct StubAdapter(Protocol);
|
struct StubAdapter(Protocol);
|
||||||
@@ -201,6 +206,7 @@ mod tests {
|
|||||||
|
|
||||||
async fn invoke_unary(
|
async fn invoke_unary(
|
||||||
&self,
|
&self,
|
||||||
|
_target: &Target,
|
||||||
_prepared: &PreparedRequest,
|
_prepared: &PreparedRequest,
|
||||||
_context: &RuntimeRequestContext,
|
_context: &RuntimeRequestContext,
|
||||||
) -> Result<AdapterResponse, ProtocolAdapterError> {
|
) -> Result<AdapterResponse, ProtocolAdapterError> {
|
||||||
@@ -244,4 +250,26 @@ mod tests {
|
|||||||
assert_eq!(scope.workspace_key, "ws_01");
|
assert_eq!(scope.workspace_key, "ws_01");
|
||||||
assert_eq!(scope.agent_key, "agent_01");
|
assert_eq!(scope.agent_key, "agent_01");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn registry_returns_registered_adapter() {
|
||||||
|
let registry = AdapterRegistry::empty().register(Arc::new(StubAdapter(Protocol::Rest)));
|
||||||
|
let adapter = registry.get(Protocol::Rest).unwrap();
|
||||||
|
|
||||||
|
let response = adapter
|
||||||
|
.invoke_unary(
|
||||||
|
&Target::Rest(RestTarget {
|
||||||
|
base_url: "https://example.test".to_owned(),
|
||||||
|
method: HttpMethod::Get,
|
||||||
|
path_template: "/health".to_owned(),
|
||||||
|
static_headers: BTreeMap::new(),
|
||||||
|
}),
|
||||||
|
&PreparedRequest::default(),
|
||||||
|
&RuntimeRequestContext::from_request_id("req_1"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(response.status_code, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user