use std::sync::Arc;
use async_trait::async_trait;
use azure_core::http::{AsyncRawResponse, Request};
use azure_core::Bytes;
use super::config::VirtualAccountConfig;
use super::dispatch::{parse_request, resolve_region};
use super::observer::RequestObserver;
use super::operations::handle_operation;
use super::store::EmulatorStore;
use crate::driver::transport::cosmos_transport_client::{
HttpRequest as DriverHttpRequest, HttpResponse as DriverHttpResponse, TransportClient,
TransportError,
};
use crate::driver::transport::http_client_factory::{HttpClientConfig, HttpClientFactory};
use crate::models::CosmosStatus;
use crate::options::ConnectionPoolOptions;
pub struct InMemoryEmulatorHttpClient {
store: Arc<EmulatorStore>,
request_observer: Option<Arc<dyn RequestObserver>>,
}
impl InMemoryEmulatorHttpClient {
pub fn new(config: VirtualAccountConfig) -> Self {
Self {
store: EmulatorStore::new(config),
request_observer: None,
}
}
pub fn store(&self) -> Arc<EmulatorStore> {
Arc::clone(&self.store)
}
pub fn with_request_observer(mut self, observer: Arc<dyn RequestObserver>) -> Self {
self.request_observer = Some(observer);
self
}
pub fn runtime_builder(self: &Arc<Self>) -> crate::driver::CosmosDriverRuntimeBuilder {
let factory = Arc::new(EmulatorHttpClientFactory {
client: Arc::clone(self),
});
crate::driver::CosmosDriverRuntimeBuilder::new().with_http_client_factory(factory)
}
#[cfg(feature = "fault_injection")]
pub fn runtime_builder_with_fault_rules(
self: &Arc<Self>,
rules: Vec<Arc<crate::fault_injection::FaultInjectionRule>>,
) -> crate::driver::CosmosDriverRuntimeBuilder {
let emulator_factory = Arc::new(EmulatorHttpClientFactory {
client: Arc::clone(self),
});
let fault_factory = Arc::new(
crate::fault_injection::FaultInjectingHttpClientFactory::new(emulator_factory, rules),
);
crate::driver::CosmosDriverRuntimeBuilder::new().with_http_client_factory(fault_factory)
}
}
impl std::fmt::Debug for InMemoryEmulatorHttpClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InMemoryEmulatorHttpClient")
.field("store", &self.store)
.finish()
}
}
impl InMemoryEmulatorHttpClient {
pub async fn execute_request(
&self,
request: &Request,
) -> crate::error::Result<AsyncRawResponse> {
if let Some(observer) = &self.request_observer {
observer.on_request(request);
}
let parsed = parse_request(request);
let region_name = match resolve_region(request.url(), self.store.config()) {
Some(r) => r,
None => {
return Err(crate::error::CosmosError::builder().with_status(crate::error::CosmosStatus::new(azure_core::http::StatusCode::BadRequest))
.with_message(format!(
"in-memory emulator: request URL host '{}' does not match any configured region",
request.url().host_str().unwrap_or("<none>"),
))
.build());
}
};
let body_bytes: Vec<u8> = Bytes::from(request.body()).to_vec();
let response = handle_operation(&self.store, region_name, &parsed, &body_bytes).await;
Ok(response)
}
}
#[derive(Debug)]
struct EmulatorHttpClientFactory {
client: Arc<InMemoryEmulatorHttpClient>,
}
impl HttpClientFactory for EmulatorHttpClientFactory {
fn build(
&self,
_connection_pool: &ConnectionPoolOptions,
_config: HttpClientConfig,
) -> crate::error::Result<Arc<dyn TransportClient>> {
Ok(Arc::new(EmulatorTransportClient {
emulator: Arc::clone(&self.client),
}))
}
}
#[derive(Debug)]
struct EmulatorTransportClient {
emulator: Arc<InMemoryEmulatorHttpClient>,
}
#[async_trait]
impl TransportClient for EmulatorTransportClient {
async fn send(
&self,
request: &DriverHttpRequest,
) -> Result<DriverHttpResponse, TransportError> {
use azure_core::http::Request;
let method = request.method;
let mut core_request = Request::new(request.url.clone(), method);
for (name, value) in request.headers.iter() {
core_request
.headers_mut()
.insert(name.clone(), value.clone());
}
if let Some(body) = &request.body {
core_request.set_body(body.to_vec());
}
let async_response = self
.emulator
.execute_request(&core_request)
.await
.map_err(|e| TransportError::new(e, crate::diagnostics::RequestSentStatus::Unknown))?;
let raw = async_response.try_into_raw_response().await.map_err(|e| {
let cosmos_err = crate::error::CosmosError::builder()
.with_status(CosmosStatus::TRANSPORT_BODY_READ_FAILED)
.with_message(e.to_string())
.with_source(e)
.build();
TransportError::new(cosmos_err, crate::diagnostics::RequestSentStatus::Sent)
})?;
let status = u16::from(raw.status());
let headers = raw.headers().clone();
let body: &[u8] = raw.body().as_ref();
Ok(DriverHttpResponse {
status,
headers,
body: body.to_vec(),
})
}
}