Files
foxhunt/services/api/src/grpc/config_proxy.rs
jgrusewski e50ea55064 feat: create services/api/ — unified gRPC gateway with tonic-web
Copied from api_gateway, removed REST handlers (port 8080),
added tonic-web + CORS for grpc-web browser access.
Binary renamed: api-gateway → api

Changes:
- Package name: api-gateway → api
- Deleted src/handlers/ (REST ML endpoints on port 8080)
- Added tonic-web 0.13 + tower-http CORS layer
- Server::builder().accept_http1(true) for grpc-web
- CORS_ORIGINS env var (default http://localhost:5173)
- Metrics server on port 9091 (axum) preserved
- All 95 lib tests pass, 0 clippy warnings
- Added services/api to workspace members

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 23:32:46 +01:00

221 lines
6.9 KiB
Rust

//! Config Service Proxy - Zero-copy gRPC forwarding for config.ConfigService
//!
//! Forwards to trading-service backend (config runs in the same process).
use futures::Stream;
use std::pin::Pin;
use tonic::{Request, Response, Status};
use tracing::{error, instrument};
use crate::config_backend::config_service_client::ConfigServiceClient;
use crate::config_backend::config_service_server::ConfigService;
use crate::config_backend::{
ConfigChangeEvent, DeleteConfigurationRequest, DeleteConfigurationResponse,
ExportConfigurationRequest, ExportConfigurationResponse, GetConfigSchemaRequest,
GetConfigSchemaResponse, GetConfigurationHistoryRequest, GetConfigurationHistoryResponse,
GetConfigurationRequest, GetConfigurationResponse, ImportConfigurationRequest,
ImportConfigurationResponse, ListCategoriesRequest, ListCategoriesResponse,
RollbackConfigurationRequest, RollbackConfigurationResponse, StreamConfigChangesRequest,
UpdateConfigSchemaRequest, UpdateConfigSchemaResponse, UpdateConfigurationRequest,
UpdateConfigurationResponse, ValidateConfigurationRequest, ValidateConfigurationResponse,
};
#[derive(Debug, Clone)]
pub struct ConfigServiceProxy {
client: ConfigServiceClient<tonic::transport::Channel>,
}
impl ConfigServiceProxy {
pub fn new(client: ConfigServiceClient<tonic::transport::Channel>) -> Self {
Self { client }
}
}
#[tonic::async_trait]
impl ConfigService for ConfigServiceProxy {
type StreamConfigChangesStream =
Pin<Box<dyn Stream<Item = Result<ConfigChangeEvent, Status>> + Send>>;
#[instrument(skip(self, request), err)]
async fn get_configuration(
&self,
request: Request<GetConfigurationRequest>,
) -> Result<Response<GetConfigurationResponse>, Status> {
self.client
.clone()
.get_configuration(request)
.await
.map_err(|e| {
error!("Backend GetConfiguration failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn update_configuration(
&self,
request: Request<UpdateConfigurationRequest>,
) -> Result<Response<UpdateConfigurationResponse>, Status> {
self.client
.clone()
.update_configuration(request)
.await
.map_err(|e| {
error!("Backend UpdateConfiguration failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn delete_configuration(
&self,
request: Request<DeleteConfigurationRequest>,
) -> Result<Response<DeleteConfigurationResponse>, Status> {
self.client
.clone()
.delete_configuration(request)
.await
.map_err(|e| {
error!("Backend DeleteConfiguration failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn list_categories(
&self,
request: Request<ListCategoriesRequest>,
) -> Result<Response<ListCategoriesResponse>, Status> {
self.client
.clone()
.list_categories(request)
.await
.map_err(|e| {
error!("Backend ListCategories failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn stream_config_changes(
&self,
request: Request<StreamConfigChangesRequest>,
) -> Result<Response<Self::StreamConfigChangesStream>, Status> {
let stream = self
.client
.clone()
.stream_config_changes(request)
.await
.map_err(|e| {
error!("Backend StreamConfigChanges failed: {}", e);
e
})?;
Ok(Response::new(Box::pin(stream.into_inner())))
}
#[instrument(skip(self, request), err)]
async fn validate_configuration(
&self,
request: Request<ValidateConfigurationRequest>,
) -> Result<Response<ValidateConfigurationResponse>, Status> {
self.client
.clone()
.validate_configuration(request)
.await
.map_err(|e| {
error!("Backend ValidateConfiguration failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn get_configuration_history(
&self,
request: Request<GetConfigurationHistoryRequest>,
) -> Result<Response<GetConfigurationHistoryResponse>, Status> {
self.client
.clone()
.get_configuration_history(request)
.await
.map_err(|e| {
error!("Backend GetConfigurationHistory failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn rollback_configuration(
&self,
request: Request<RollbackConfigurationRequest>,
) -> Result<Response<RollbackConfigurationResponse>, Status> {
self.client
.clone()
.rollback_configuration(request)
.await
.map_err(|e| {
error!("Backend RollbackConfiguration failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn export_configuration(
&self,
request: Request<ExportConfigurationRequest>,
) -> Result<Response<ExportConfigurationResponse>, Status> {
self.client
.clone()
.export_configuration(request)
.await
.map_err(|e| {
error!("Backend ExportConfiguration failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn import_configuration(
&self,
request: Request<ImportConfigurationRequest>,
) -> Result<Response<ImportConfigurationResponse>, Status> {
self.client
.clone()
.import_configuration(request)
.await
.map_err(|e| {
error!("Backend ImportConfiguration failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn get_config_schema(
&self,
request: Request<GetConfigSchemaRequest>,
) -> Result<Response<GetConfigSchemaResponse>, Status> {
self.client
.clone()
.get_config_schema(request)
.await
.map_err(|e| {
error!("Backend GetConfigSchema failed: {}", e);
e
})
}
#[instrument(skip(self, request), err)]
async fn update_config_schema(
&self,
request: Request<UpdateConfigSchemaRequest>,
) -> Result<Response<UpdateConfigSchemaResponse>, Status> {
self.client
.clone()
.update_config_schema(request)
.await
.map_err(|e| {
error!("Backend UpdateConfigSchema failed: {}", e);
e
})
}
}