From ae14148d516ee4077a5e7837d1f3ee1dc831969d Mon Sep 17 00:00:00 2001 From: Peter Wilson Date: Fri, 21 Nov 2025 15:03:02 +0000 Subject: [PATCH] Add generated proto files for v0.0.1 release Include proto and generated code in repository to fix cargo publish verification. This follows the same pattern as Python and Go SDKs. --- .gitignore | 6 - build.rs | 15 +- proto/plugin.proto | 91 ++++ src/generated/mozilla.mcpd.plugins.v1.rs | 609 +++++++++++++++++++++++ 4 files changed, 714 insertions(+), 7 deletions(-) create mode 100644 proto/plugin.proto create mode 100644 src/generated/mozilla.mcpd.plugins.v1.rs diff --git a/.gitignore b/.gitignore index ec694dd..6239a78 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,6 @@ /target/ Cargo.lock -# Generated code. -/src/generated/ - -# Downloaded proto files. -/proto/ - # IDE files. .vscode/ .idea/ diff --git a/build.rs b/build.rs index 03dae7a..a0eac01 100644 --- a/build.rs +++ b/build.rs @@ -7,10 +7,22 @@ fn main() -> Result<(), Box> { println!("cargo:rerun-if-changed=proto/plugin.proto"); println!("cargo:rerun-if-env-changed=PROTO_VERSION"); + println!("cargo:rerun-if-env-changed=FORCE_CODEGEN"); - // Download proto file if it doesn't exist. let proto_path = PathBuf::from("proto/plugin.proto"); + let generated_file = PathBuf::from("src/generated/mozilla.mcpd.plugins.v1.rs"); + + // Check if we need to regenerate code. + // Skip generation if both proto and generated files exist, unless FORCE_CODEGEN is set. + let force_codegen = env::var("FORCE_CODEGEN").is_ok(); + let needs_generation = force_codegen || !proto_path.exists() || !generated_file.exists(); + if !needs_generation { + eprintln!("Using existing generated code (set FORCE_CODEGEN=1 to regenerate)"); + return Ok(()); + } + + // Download proto file if it doesn't exist. if !proto_path.exists() { eprintln!("Downloading plugin.proto version {}...", proto_version); std::fs::create_dir_all("proto")?; @@ -33,6 +45,7 @@ fn main() -> Result<(), Box> { std::fs::create_dir_all(&out_dir)?; // Configure protobuf compilation. + eprintln!("Generating Rust code from protobuf..."); tonic_build::configure() .build_server(true) .build_client(false) diff --git a/proto/plugin.proto b/proto/plugin.proto new file mode 100644 index 0000000..aac25de --- /dev/null +++ b/proto/plugin.proto @@ -0,0 +1,91 @@ +syntax = "proto3"; + +package mozilla.mcpd.plugins.v1; + +// Go Option: This option tells the Go compiler where to find the generated code. +// This is what plugin developers writing in Go will reference. +option go_package = "github.com/mozilla-ai/mcpd-plugins-sdk-go/pkg/plugins/v1;v1"; + +// C# Option: Defines the namespace for the generated classes. +// This is the namespace developers using the C# plugin SDK will reference. +option csharp_namespace = "MozillaAI.Mcpd.Plugins.V1"; + +import "google/protobuf/empty.proto"; + +// Metadata about the plugin. +message Metadata { + string name = 1; + string version = 2; + string description = 3; + string commit_hash = 4; + string build_date = 5; +} + +// Flow supported by the plugin. +enum Flow { + FLOW_REQUEST = 0; + FLOW_RESPONSE = 1; +} + +// Capabilities declares which flows a plugin supports. +// The repeated field represents a set (no duplicates expected). +message Capabilities { + repeated Flow flows = 1; +} + +// HTTPRequest represents an HTTP request for plugin processing. +message HTTPRequest { + string method = 1; + string url = 2; + string path = 3; + map headers = 4; // Simplified: first value only per header + bytes body = 5; + string remote_addr = 6; + string request_uri = 7; +} + +// HTTPResponse represents an HTTP response from plugin processing. +message HTTPResponse { + int32 status_code = 1; + map headers = 2; + bytes body = 3; + bool continue = 4; // If true, continue to next plugin/handler; if false, short-circuit + + // Optional modified request for plugins that want to transform the incoming request. + // When set, contains the modified version of the request that was processed. + HTTPRequest modified_request = 5; +} + +// TelemetryConfig provides OpenTelemetry configuration. +message TelemetryConfig { + string otlp_endpoint = 1; + string service_name = 2; + string environment = 3; + double sample_ratio = 4; +} + +// PluginConfig contains host-provided configuration for the plugin. +message PluginConfig { + TelemetryConfig telemetry = 1; + map custom_config = 2; // Plugin-specific config from YAML +} + +// Plugin service. +service Plugin { + // Lifecycle + rpc Configure(PluginConfig) returns (google.protobuf.Empty); + rpc Stop(google.protobuf.Empty) returns (google.protobuf.Empty); + + // Identity and capabilities + rpc GetMetadata(google.protobuf.Empty) returns (Metadata); + rpc GetCapabilities(google.protobuf.Empty) returns (Capabilities); + + // Health / readiness + // Returns error via gRPC status if unhealthy or not ready. + rpc CheckHealth(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc CheckReady(google.protobuf.Empty) returns (google.protobuf.Empty); + + // Request / response handling + rpc HandleRequest(HTTPRequest) returns (HTTPResponse); + rpc HandleResponse(HTTPResponse) returns (HTTPResponse); +} diff --git a/src/generated/mozilla.mcpd.plugins.v1.rs b/src/generated/mozilla.mcpd.plugins.v1.rs new file mode 100644 index 0000000..c317f18 --- /dev/null +++ b/src/generated/mozilla.mcpd.plugins.v1.rs @@ -0,0 +1,609 @@ +// This file is @generated by prost-build. +/// Metadata about the plugin. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Metadata { + #[prost(string, tag = "1")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub version: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub description: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub commit_hash: ::prost::alloc::string::String, + #[prost(string, tag = "5")] + pub build_date: ::prost::alloc::string::String, +} +/// Capabilities declares which flows a plugin supports. +/// The repeated field represents a set (no duplicates expected). +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Capabilities { + #[prost(enumeration = "Flow", repeated, tag = "1")] + pub flows: ::prost::alloc::vec::Vec, +} +/// HTTPRequest represents an HTTP request for plugin processing. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HttpRequest { + #[prost(string, tag = "1")] + pub method: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub url: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, + /// Simplified: first value only per header + #[prost(map = "string, string", tag = "4")] + pub headers: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, + #[prost(bytes = "vec", tag = "5")] + pub body: ::prost::alloc::vec::Vec, + #[prost(string, tag = "6")] + pub remote_addr: ::prost::alloc::string::String, + #[prost(string, tag = "7")] + pub request_uri: ::prost::alloc::string::String, +} +/// HTTPResponse represents an HTTP response from plugin processing. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HttpResponse { + #[prost(int32, tag = "1")] + pub status_code: i32, + #[prost(map = "string, string", tag = "2")] + pub headers: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, + #[prost(bytes = "vec", tag = "3")] + pub body: ::prost::alloc::vec::Vec, + /// If true, continue to next plugin/handler; if false, short-circuit + #[prost(bool, tag = "4")] + pub r#continue: bool, + /// Optional modified request for plugins that want to transform the incoming request. + /// When set, contains the modified version of the request that was processed. + #[prost(message, optional, tag = "5")] + pub modified_request: ::core::option::Option, +} +/// TelemetryConfig provides OpenTelemetry configuration. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct TelemetryConfig { + #[prost(string, tag = "1")] + pub otlp_endpoint: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub service_name: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub environment: ::prost::alloc::string::String, + #[prost(double, tag = "4")] + pub sample_ratio: f64, +} +/// PluginConfig contains host-provided configuration for the plugin. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PluginConfig { + #[prost(message, optional, tag = "1")] + pub telemetry: ::core::option::Option, + /// Plugin-specific config from YAML + #[prost(map = "string, string", tag = "2")] + pub custom_config: ::std::collections::HashMap< + ::prost::alloc::string::String, + ::prost::alloc::string::String, + >, +} +/// Flow supported by the plugin. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum Flow { + Request = 0, + Response = 1, +} +impl Flow { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Request => "FLOW_REQUEST", + Self::Response => "FLOW_RESPONSE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "FLOW_REQUEST" => Some(Self::Request), + "FLOW_RESPONSE" => Some(Self::Response), + _ => None, + } + } +} +/// Generated server implementations. +pub mod plugin_server { + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] + use tonic::codegen::*; + /// Generated trait containing gRPC methods that should be implemented for use with PluginServer. + #[async_trait] + pub trait Plugin: std::marker::Send + std::marker::Sync + 'static { + /// Lifecycle + async fn configure( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn stop( + &self, + request: tonic::Request<()>, + ) -> std::result::Result, tonic::Status>; + /// Identity and capabilities + async fn get_metadata( + &self, + request: tonic::Request<()>, + ) -> std::result::Result, tonic::Status>; + async fn get_capabilities( + &self, + request: tonic::Request<()>, + ) -> std::result::Result, tonic::Status>; + /// Health / readiness + /// Returns error via gRPC status if unhealthy or not ready. + async fn check_health( + &self, + request: tonic::Request<()>, + ) -> std::result::Result, tonic::Status>; + async fn check_ready( + &self, + request: tonic::Request<()>, + ) -> std::result::Result, tonic::Status>; + /// Request / response handling + async fn handle_request( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + async fn handle_response( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status>; + } + /// Plugin service. + #[derive(Debug)] + pub struct PluginServer { + inner: Arc, + accept_compression_encodings: EnabledCompressionEncodings, + send_compression_encodings: EnabledCompressionEncodings, + max_decoding_message_size: Option, + max_encoding_message_size: Option, + } + impl PluginServer { + pub fn new(inner: T) -> Self { + Self::from_arc(Arc::new(inner)) + } + pub fn from_arc(inner: Arc) -> Self { + Self { + inner, + accept_compression_encodings: Default::default(), + send_compression_encodings: Default::default(), + max_decoding_message_size: None, + max_encoding_message_size: None, + } + } + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService + where + F: tonic::service::Interceptor, + { + InterceptedService::new(Self::new(inner), interceptor) + } + /// Enable decompressing requests with the given encoding. + #[must_use] + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.accept_compression_encodings.enable(encoding); + self + } + /// Compress responses with the given encoding, if the client supports it. + #[must_use] + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { + self.send_compression_encodings.enable(encoding); + self + } + /// Limits the maximum size of a decoded message. + /// + /// Default: `4MB` + #[must_use] + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { + self.max_decoding_message_size = Some(limit); + self + } + /// Limits the maximum size of an encoded message. + /// + /// Default: `usize::MAX` + #[must_use] + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { + self.max_encoding_message_size = Some(limit); + self + } + } + impl tonic::codegen::Service> for PluginServer + where + T: Plugin, + B: Body + std::marker::Send + 'static, + B::Error: Into + std::marker::Send + 'static, + { + type Response = http::Response; + type Error = std::convert::Infallible; + type Future = BoxFuture; + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + fn call(&mut self, req: http::Request) -> Self::Future { + match req.uri().path() { + "/mozilla.mcpd.plugins.v1.Plugin/Configure" => { + #[allow(non_camel_case_types)] + struct ConfigureSvc(pub Arc); + impl tonic::server::UnaryService + for ConfigureSvc { + type Response = (); + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::configure(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = ConfigureSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/mozilla.mcpd.plugins.v1.Plugin/Stop" => { + #[allow(non_camel_case_types)] + struct StopSvc(pub Arc); + impl tonic::server::UnaryService<()> for StopSvc { + type Response = (); + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call(&mut self, request: tonic::Request<()>) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::stop(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = StopSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/mozilla.mcpd.plugins.v1.Plugin/GetMetadata" => { + #[allow(non_camel_case_types)] + struct GetMetadataSvc(pub Arc); + impl tonic::server::UnaryService<()> + for GetMetadataSvc { + type Response = super::Metadata; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call(&mut self, request: tonic::Request<()>) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_metadata(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetMetadataSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/mozilla.mcpd.plugins.v1.Plugin/GetCapabilities" => { + #[allow(non_camel_case_types)] + struct GetCapabilitiesSvc(pub Arc); + impl tonic::server::UnaryService<()> + for GetCapabilitiesSvc { + type Response = super::Capabilities; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call(&mut self, request: tonic::Request<()>) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::get_capabilities(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = GetCapabilitiesSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/mozilla.mcpd.plugins.v1.Plugin/CheckHealth" => { + #[allow(non_camel_case_types)] + struct CheckHealthSvc(pub Arc); + impl tonic::server::UnaryService<()> + for CheckHealthSvc { + type Response = (); + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call(&mut self, request: tonic::Request<()>) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::check_health(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CheckHealthSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/mozilla.mcpd.plugins.v1.Plugin/CheckReady" => { + #[allow(non_camel_case_types)] + struct CheckReadySvc(pub Arc); + impl tonic::server::UnaryService<()> + for CheckReadySvc { + type Response = (); + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call(&mut self, request: tonic::Request<()>) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::check_ready(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CheckReadySvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/mozilla.mcpd.plugins.v1.Plugin/HandleRequest" => { + #[allow(non_camel_case_types)] + struct HandleRequestSvc(pub Arc); + impl tonic::server::UnaryService + for HandleRequestSvc { + type Response = super::HttpResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::handle_request(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = HandleRequestSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/mozilla.mcpd.plugins.v1.Plugin/HandleResponse" => { + #[allow(non_camel_case_types)] + struct HandleResponseSvc(pub Arc); + impl tonic::server::UnaryService + for HandleResponseSvc { + type Response = super::HttpResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::handle_response(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = HandleResponseSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } + } + } + } + impl Clone for PluginServer { + fn clone(&self) -> Self { + let inner = self.inner.clone(); + Self { + inner, + accept_compression_encodings: self.accept_compression_encodings, + send_compression_encodings: self.send_compression_encodings, + max_decoding_message_size: self.max_decoding_message_size, + max_encoding_message_size: self.max_encoding_message_size, + } + } + } + /// Generated gRPC service name + pub const SERVICE_NAME: &str = "mozilla.mcpd.plugins.v1.Plugin"; + impl tonic::server::NamedService for PluginServer { + const NAME: &'static str = SERVICE_NAME; + } +}