feat: add grpc support

This commit is contained in:
a.tolmachev
2026-03-25 21:23:57 +03:00
parent ada2436e54
commit 8005510ad2
33 changed files with 1725 additions and 32 deletions
+178 -1
View File
@@ -5,10 +5,11 @@ use mcpaas_core::{
GeneratedDraftStatus, OperationId, OperationStatus, Protocol, SampleId, Samples, Target,
};
use mcpaas_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples};
use mcpaas_proto::{ProtoService, services_from_descriptor_set_bytes};
use mcpaas_registry::{
CreateVersionRequest, OperationSampleMetadata, OperationSummary, OperationVersionRecord,
PostgresRegistry, PublishRequest, RegistryOperation, SampleKind, SaveAuthProfileRequest,
SaveSampleMetadataRequest,
SaveDescriptorMetadataRequest, SaveSampleMetadataRequest,
};
use mcpaas_runtime::{PreparedRequest, RuntimeError, RuntimeExecutor, RuntimeOperation};
use mcpaas_schema::Schema;
@@ -139,6 +140,27 @@ pub struct ImportResponse {
pub warnings: Vec<String>,
}
#[derive(Clone, Debug, Serialize)]
pub struct DescriptorUploadResponse {
pub descriptor_id: String,
pub version: u32,
}
#[derive(Clone, Debug, Serialize)]
pub struct GrpcServiceSummary {
pub package: String,
pub service: String,
pub methods: Vec<GrpcMethodSummary>,
}
#[derive(Clone, Debug, Serialize)]
pub struct GrpcMethodSummary {
pub name: String,
pub kind: String,
pub input_schema: Schema,
pub output_schema: Schema,
}
impl AdminService {
pub fn new(registry: PostgresRegistry, storage_root: PathBuf) -> Self {
Self {
@@ -345,6 +367,138 @@ impl AdminService {
Ok(self.registry.list_auth_profiles().await?)
}
pub async fn upload_descriptor_set(
&self,
operation_id: &OperationId,
source_name: Option<&str>,
payload: &[u8],
) -> Result<DescriptorUploadResponse, ApiError> {
let summary = self.get_operation(operation_id).await?;
if summary.protocol != Protocol::Grpc {
return Err(ApiError::validation(
"descriptor upload is only allowed for grpc operations",
));
}
let services = services_from_descriptor_set_bytes(payload)
.map_err(|error| ApiError::validation(error.to_string()))?;
let descriptor_id = mcpaas_core::DescriptorId::new(new_prefixed_id("desc"));
let storage_ref = self
.storage
.write_descriptor(
operation_id,
summary.current_draft_version,
mcpaas_registry::DescriptorKind::DescriptorSet,
&descriptor_id,
source_name,
payload,
)
.await?;
self.registry
.save_descriptor_metadata(SaveDescriptorMetadataRequest {
descriptor: &mcpaas_registry::DescriptorMetadata {
id: descriptor_id.clone(),
operation_id: Some(operation_id.clone()),
version: Some(summary.current_draft_version),
descriptor_kind: mcpaas_registry::DescriptorKind::DescriptorSet,
storage_ref,
source_name: source_name.map(ToOwned::to_owned),
package_index: Some(
serde_json::to_value(&services)
.map_err(|error| ApiError::internal(error.to_string()))?,
),
created_at: now_string()?,
},
})
.await?;
Ok(DescriptorUploadResponse {
descriptor_id: descriptor_id.as_str().to_owned(),
version: summary.current_draft_version,
})
}
pub async fn upload_proto_file(
&self,
operation_id: &OperationId,
source_name: Option<&str>,
payload: &[u8],
) -> Result<DescriptorUploadResponse, ApiError> {
let summary = self.get_operation(operation_id).await?;
if summary.protocol != Protocol::Grpc {
return Err(ApiError::validation(
"proto upload is only allowed for grpc operations",
));
}
let descriptor_id = mcpaas_core::DescriptorId::new(new_prefixed_id("desc"));
let storage_ref = self
.storage
.write_descriptor(
operation_id,
summary.current_draft_version,
mcpaas_registry::DescriptorKind::ProtoUpload,
&descriptor_id,
source_name,
payload,
)
.await?;
self.registry
.save_descriptor_metadata(SaveDescriptorMetadataRequest {
descriptor: &mcpaas_registry::DescriptorMetadata {
id: descriptor_id.clone(),
operation_id: Some(operation_id.clone()),
version: Some(summary.current_draft_version),
descriptor_kind: mcpaas_registry::DescriptorKind::ProtoUpload,
storage_ref,
source_name: source_name.map(ToOwned::to_owned),
package_index: None,
created_at: now_string()?,
},
})
.await?;
Ok(DescriptorUploadResponse {
descriptor_id: descriptor_id.as_str().to_owned(),
version: summary.current_draft_version,
})
}
pub async fn list_grpc_services(
&self,
operation_id: &OperationId,
version: Option<u32>,
) -> Result<Vec<GrpcServiceSummary>, ApiError> {
let summary = self.get_operation(operation_id).await?;
if summary.protocol != Protocol::Grpc {
return Err(ApiError::validation(
"grpc services are only available for grpc operations",
));
}
let version = version.unwrap_or(summary.current_draft_version);
let descriptor = self
.registry
.list_descriptor_metadata(operation_id, version)
.await?
.into_iter()
.rev()
.find(|descriptor| {
descriptor.descriptor_kind == mcpaas_registry::DescriptorKind::DescriptorSet
})
.ok_or_else(|| ApiError::not_found("descriptor set was not uploaded"))?;
let bytes = self.storage.read_bytes(&descriptor.storage_ref).await?;
let services = services_from_descriptor_set_bytes(&bytes)
.map_err(|error| ApiError::validation(error.to_string()))?;
services
.into_iter()
.map(proto_service_summary)
.collect::<Result<Vec<_>, _>>()
}
pub async fn get_auth_profile(
&self,
auth_profile_id: &AuthProfileId,
@@ -612,6 +766,7 @@ fn build_request_preview(
"path": prepared.path_params,
"query": prepared.query_params,
"headers": prepared.headers,
"grpc": prepared.grpc.unwrap_or(Value::Null),
"variables": prepared.variables.unwrap_or(Value::Null),
"body": prepared.body.unwrap_or(Value::Null)
}))
@@ -672,3 +827,25 @@ fn now_string() -> Result<String, ApiError> {
.format(&Rfc3339)
.map_err(|error| ApiError::internal(error.to_string()))
}
fn proto_service_summary(service: ProtoService) -> Result<GrpcServiceSummary, ApiError> {
let methods = service
.unary_methods()
.map(|method| {
Ok(GrpcMethodSummary {
name: method.name.clone(),
kind: "unary".to_owned(),
input_schema: mcpaas_proto::message_to_schema(&method.input)
.map_err(|error| ApiError::validation(error.to_string()))?,
output_schema: mcpaas_proto::message_to_schema(&method.output)
.map_err(|error| ApiError::validation(error.to_string()))?,
})
})
.collect::<Result<Vec<_>, ApiError>>()?;
Ok(GrpcServiceSummary {
package: service.package,
service: service.name,
methods,
})
}